1 /******************************************************************************* 2 3 Define the base classes for all Appenders 4 5 Appenders are objects that are responsible for emitting messages sent 6 to a particular logger. There may be more than one appender attached 7 to any logger. 8 The actual message is constructed by another class known as an EventLayout. 9 10 Copyright: 11 Copyright (c) 2004 Kris Bell. 12 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 13 All rights reserved. 14 15 License: 16 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 17 See LICENSE_TANGO.txt for details. 18 19 *******************************************************************************/ 20 21 module ocean.util.log.Appender; 22 23 import ocean.core.Verify; 24 import ocean.core.ExceptionDefinitions; 25 import ocean.io.model.IConduit; 26 import ocean.text.convert.Formatter; 27 import ocean.meta.types.Qualifiers; 28 import ocean.meta.types.Typedef; 29 import ocean.util.log.Event; 30 import ocean.util.log.ILogger; 31 32 version (unittest) 33 { 34 import ocean.core.Test; 35 import ocean.time.Clock; 36 import ocean.time.Time; 37 import ocean.util.log.Hierarchy; 38 import ocean.util.log.Logger; 39 } 40 41 42 /// Base class for all Appenders 43 public class Appender 44 { 45 mixin(Typedef!(int, "Mask")); 46 47 private Appender next_; 48 private ILogger.Level level_; 49 private Layout layout_; 50 private static Layout generic; 51 52 /*************************************************************************** 53 54 Interface for all logging layout instances 55 56 Implement this method to perform the formatting of message content. 57 58 ***************************************************************************/ 59 60 public interface Layout 61 { 62 /// Convenience alias for implementing classes 63 protected alias FormatterSink = .FormatterSink; 64 65 /*********************************************************************** 66 67 Format the provided `event` to the `sink` using `this` layout 68 69 Params: 70 event = The log event to format 71 sink = Where to output the result 72 73 ***********************************************************************/ 74 75 void format (LogEvent event, scope FormatterSink sink); 76 } 77 78 /*************************************************************************** 79 80 Return the mask used to identify this Appender. 81 82 The mask is used to figure out whether an appender has already been 83 invoked for a particular logger. 84 85 ***************************************************************************/ 86 87 abstract Mask mask (); 88 89 /// Return the name of this Appender. 90 abstract cstring name (); 91 92 /*************************************************************************** 93 94 Append a message to the output. 95 96 The event received is only valid for the duration of the `apppend` 97 call and shouldn't outlive the scope of `append`. 98 Moreover, as `Logger` use a class-local buffer, its tracing functions 99 which use formatting are not re-entrant and should not be called 100 from here. 101 102 Params: 103 event = Event to log 104 105 ***************************************************************************/ 106 107 abstract void append (LogEvent event); 108 109 /// Create an Appender and default its layout to LayoutTimer. 110 public this () 111 { 112 if (generic is null) 113 generic = new LayoutTimer; 114 this.layout_ = generic; 115 } 116 117 /// Return the current Level setting 118 final ILogger.Level level () 119 { 120 return this.level_; 121 } 122 123 /// Return the current Level setting 124 final Appender level (ILogger.Level l) 125 { 126 this.level_ = l; 127 return this; 128 } 129 130 /*************************************************************************** 131 132 Static method to return a mask for identifying the Appender. 133 134 Each Appender class should have a unique fingerprint so that we can 135 figure out which ones have been invoked for a given event. 136 A bitmask is a simple an efficient way to do that. 137 138 ***************************************************************************/ 139 140 protected Mask register (cstring tag) 141 { 142 static Mask mask = 1; 143 static Mask[istring] registry; 144 145 Mask* p = tag in registry; 146 if (p) 147 return *p; 148 else 149 { 150 auto ret = mask; 151 registry [tag] = mask; 152 153 if (mask < 0) 154 throw new IllegalArgumentException ("too many unique registrations"); 155 156 mask <<= 1; 157 return ret; 158 } 159 } 160 161 /*************************************************************************** 162 163 Set the current layout to be that of the argument, or the generic layout 164 where the argument is null 165 166 ***************************************************************************/ 167 168 void layout (Layout how) 169 { 170 verify(generic !is null); 171 this.layout_ = how ? how : generic; 172 } 173 174 /// Return the current Layout 175 Layout layout () 176 { 177 return this.layout_; 178 } 179 180 /// Attach another appender to this one 181 void next (Appender appender) 182 { 183 this.next_ = appender; 184 } 185 186 /// Return the next appender in the list 187 Appender next () 188 { 189 return this.next_; 190 } 191 192 /// Close this appender. This would be used for file, sockets, and the like 193 void close () 194 { 195 } 196 } 197 198 199 /******************************************************************************* 200 201 An appender that does nothing. 202 203 This is useful for cutting and pasting, and for benchmarking the ocean.log 204 environment. 205 206 *******************************************************************************/ 207 208 public class AppendNull : Appender 209 { 210 private Mask mask_; 211 212 /// Create with the given Layout 213 this (Layout how = null) 214 { 215 this.mask_ = this.register(name); 216 this.layout(how); 217 } 218 219 /// Return the fingerprint for this class 220 final override Mask mask () 221 { 222 return this.mask_; 223 } 224 225 /// Return the name of this class 226 final override cstring name () 227 { 228 return this.classinfo.name; 229 } 230 231 /// Append an event to the output 232 final override void append (LogEvent event) 233 { 234 this.layout.format(event, (cstring) {}); 235 } 236 } 237 238 239 /// Append to a configured OutputStream 240 public class AppendStream : Appender 241 { 242 private Mask mask_; 243 private bool flush_; 244 private OutputStream stream_; 245 246 ///Create with the given stream and layout 247 this (OutputStream stream, bool flush = false, Appender.Layout how = null) 248 { 249 verify(stream !is null); 250 251 this.mask_ = register (name); 252 this.stream_ = stream; 253 this.flush_ = flush; 254 this.layout(how); 255 } 256 257 /// Return the fingerprint for this class 258 final override Mask mask () 259 { 260 return this.mask_; 261 } 262 263 /// Return the name of this class 264 override istring name () 265 { 266 return this.classinfo.name; 267 } 268 269 /// Append an event to the output. 270 final override void append (LogEvent event) 271 { 272 static immutable istring Eol = "\n"; 273 274 this.layout.format(event, (cstring content) { this.stream_.write(content); }); 275 this.stream_.write(Eol); 276 if (this.flush_) 277 this.stream_.flush; 278 } 279 } 280 281 282 /******************************************************************************* 283 284 A simple layout comprised only of time(ms), level, name, and message 285 286 *******************************************************************************/ 287 288 public class LayoutTimer : Appender.Layout 289 { 290 /*************************************************************************** 291 292 Subclasses should implement this method to perform the formatting 293 of the actual message content. 294 295 ***************************************************************************/ 296 297 public override void format (LogEvent event, scope FormatterSink dg) 298 { 299 sformat(dg, "{} {} [{}] {}- {}", event.span.millis(), event.levelName, 300 event.name, event.host.label, event); 301 } 302 } 303 304 unittest 305 { 306 mstring result = new mstring(2048); 307 result.length = 0; 308 assumeSafeAppend(result); 309 310 scope dg = (cstring v) { result ~= v; }; 311 scope layout = new LayoutTimer(); 312 LogEvent event = { 313 msg_: "Have you met Ted?", 314 name_: "Barney", 315 time_: Clock.startTime() + TimeSpan.fromMillis(420), 316 level_: ILogger.Level.Warn, 317 host_: new HierarchyT!(Logger)("test"), 318 }; 319 320 testNoAlloc(layout.format(event, dg)); 321 test!("==")(result, "420 Warn [Barney] test- Have you met Ted?"); 322 }