1+ // Copyright 2016 Serilog Contributors
2+ //
3+ // Licensed under the Apache License, Version 2.0 (the "License");
4+ // you may not use this file except in compliance with the License.
5+ // You may obtain a copy of the License at
6+ //
7+ // http://www.apache.org/licenses/LICENSE-2.0
8+ //
9+ // Unless required by applicable law or agreed to in writing, software
10+ // distributed under the License is distributed on an "AS IS" BASIS,
11+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ // See the License for the specific language governing permissions and
13+ // limitations under the License.
14+
15+ using Serilog . Events ;
16+ using Serilog . Formatting ;
17+ using Serilog . Formatting . Json ;
18+ using Serilog . Parsing ;
19+ using System ;
20+ using System . Globalization ;
21+ using System . IO ;
22+ using System . Linq ;
23+
24+ namespace Serilog . Sinks . Splunk
25+ {
26+ /// <summary>
27+ /// Renders log events into a Compact JSON format for consumption by Splunk.
28+ /// </summary>
29+ public class CompactSplunkJsonFormatter : ITextFormatter
30+ {
31+ private static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter ( typeTagName : "$type" ) ;
32+ private readonly string _suffix ;
33+ private readonly bool _renderTemplate ;
34+
35+ /// <summary>
36+ /// Construct a <see cref="CompactSplunkJsonFormatter"/>.
37+ /// </summary>
38+ /// <param name="source">The source of the event</param>
39+ /// <param name="sourceType">The source type of the event</param>
40+ /// <param name="host">The host of the event</param>
41+ /// <param name="index">The Splunk index to log to</param>
42+ /// <param name="renderTemplate">If true, the template used will be rendered and written to the output as a property named MessageTemplate</param>
43+ public CompactSplunkJsonFormatter ( bool renderTemplate = false , string source = null , string sourceType = null , string host = null , string index = null )
44+ {
45+ _renderTemplate = renderTemplate ;
46+ var suffixWriter = new StringWriter ( ) ;
47+ suffixWriter . Write ( "}" ) ; // Terminates "event"
48+
49+ if ( ! string . IsNullOrWhiteSpace ( source ) )
50+ {
51+ suffixWriter . Write ( ",\" source\" :" ) ;
52+ JsonValueFormatter . WriteQuotedJsonString ( source , suffixWriter ) ;
53+ }
54+
55+ if ( ! string . IsNullOrWhiteSpace ( sourceType ) )
56+ {
57+ suffixWriter . Write ( ",\" sourcetype\" :" ) ;
58+ JsonValueFormatter . WriteQuotedJsonString ( sourceType , suffixWriter ) ;
59+ }
60+
61+ if ( ! string . IsNullOrWhiteSpace ( host ) )
62+ {
63+ suffixWriter . Write ( ",\" host\" :" ) ;
64+ JsonValueFormatter . WriteQuotedJsonString ( host , suffixWriter ) ;
65+ }
66+
67+ if ( ! string . IsNullOrWhiteSpace ( index ) )
68+ {
69+ suffixWriter . Write ( ",\" index\" :" ) ;
70+ JsonValueFormatter . WriteQuotedJsonString ( index , suffixWriter ) ;
71+ }
72+ suffixWriter . Write ( '}' ) ; // Terminates the payload
73+ _suffix = suffixWriter . ToString ( ) ;
74+ }
75+
76+ /// <inheritdoc/>
77+ public void Format ( LogEvent logEvent , TextWriter output )
78+ {
79+ if ( logEvent == null ) throw new ArgumentNullException ( nameof ( logEvent ) ) ;
80+ if ( output == null ) throw new ArgumentNullException ( nameof ( output ) ) ;
81+
82+ output . Write ( "{\" time\" :\" " ) ;
83+ output . Write ( logEvent . Timestamp . ToEpoch ( ) . ToString ( CultureInfo . InvariantCulture ) ) ;
84+ output . Write ( "\" ,\" event\" :{\" @l\" :\" " ) ;
85+ output . Write ( logEvent . Level ) ;
86+ output . Write ( '"' ) ;
87+
88+ if ( _renderTemplate )
89+ {
90+ output . Write ( ",\" @mt\" :" ) ;
91+ JsonValueFormatter . WriteQuotedJsonString ( logEvent . MessageTemplate . Text , output ) ;
92+
93+ var tokensWithFormat = logEvent . MessageTemplate . Tokens
94+ . OfType < PropertyToken > ( )
95+ . Where ( pt => pt . Format != null ) ;
96+
97+ // Better not to allocate an array in the 99.9% of cases where this is false
98+ // ReSharper disable once PossibleMultipleEnumeration
99+ if ( tokensWithFormat . Any ( ) )
100+ {
101+ output . Write ( ",\" @r\" :[" ) ;
102+ var delim = "" ;
103+ foreach ( var r in tokensWithFormat )
104+ {
105+ output . Write ( delim ) ;
106+ delim = "," ;
107+ var space = new StringWriter ( ) ;
108+ r . Render ( logEvent . Properties , space ) ;
109+ JsonValueFormatter . WriteQuotedJsonString ( space . ToString ( ) , output ) ;
110+ }
111+ output . Write ( ']' ) ;
112+ }
113+ }
114+ if ( logEvent . Exception != null )
115+ {
116+ output . Write ( ",\" @x\" :" ) ;
117+ JsonValueFormatter . WriteQuotedJsonString ( logEvent . Exception . ToString ( ) , output ) ;
118+ }
119+
120+ foreach ( var property in logEvent . Properties )
121+ {
122+ var name = property . Key ;
123+ if ( name . Length > 0 && name [ 0 ] == '@' )
124+ {
125+ // Escape first '@' by doubling
126+ name = '@' + name ;
127+ }
128+
129+ output . Write ( ',' ) ;
130+ JsonValueFormatter . WriteQuotedJsonString ( name , output ) ;
131+ output . Write ( ':' ) ;
132+ ValueFormatter . Format ( property . Value , output ) ;
133+ }
134+ output . WriteLine ( _suffix ) ;
135+ }
136+ }
137+ }
0 commit comments