github.com/qioalice/ekago/v3@v3.3.2-0.20221202205325-5c262d586ee4/ekalog/encoder_console.go (about) 1 // Copyright © 2020-2021. All rights reserved. 2 // Author: Ilya Stroy. 3 // Contacts: iyuryevich@pm.me, https://github.com/qioalice 4 // License: https://opensource.org/licenses/MIT 5 6 package ekalog 7 8 import ( 9 "io" 10 "strings" 11 12 "github.com/qioalice/ekago/v3/internal/ekaletter" 13 ) 14 15 //noinspection GoSnakeCaseUsage 16 type ( 17 // CI_ConsoleEncoder is a type that built to be used as a part of CommonIntegrator 18 // as an log Entry encoder to the TTY (console) with human readable format, 19 // custom format supporting and ability to enable coloring. 20 // 21 // If you want to use CI_ConsoleEncoder, you need to instantiate object, 22 // set log's text format string (if you dont want to use default one) 23 // using SetFormat() and that is. 24 // The last thing you need to do is to register CI_ConsoleEncoder with 25 // CommonIntegrator using CommonIntegrator.WithEncoder(). 26 // 27 // See SetFormat() docs to figure out how you can manage the log format. 28 // 29 // CI_ConsoleEncoder may look like heavy rock and it's only half true. 30 // It's really flexible customizable text encoder, but the format string parsing 31 // is performed only once. At the registration of CI_ConsoleEncoder. 32 // Encode operations will be performed over split and parsed format string, 33 // and it's as blazing fast as it's even possible. 34 // 35 // You MUST NOT to call EncodeEntry() method manually. 36 // It is used by associated CommonIntegrator and it WILL lead to UB 37 // if you will try to use it manually. May even panic. 38 CI_ConsoleEncoder struct { 39 40 // RAW FORMAT 41 // This is what user is set by SetFormat() method. 42 // 43 // It will be parsed, converted and casted to internal structures 44 // that are more convenient to be used as source of log message format 45 // generator. 46 format string 47 48 // BUILT FORMAT 49 // This part represents parsed raw format string. 50 // 51 // First of all RAW format string parsed to the two group of entities: 52 // - Just string, not verb (writes as is), 53 // - Format verbs (will be substituted to the log's parts, such as 54 // log's message, timestamp, log's level, log's fields, stacktrace, etc). 55 // 56 // These parts (w/o reallocation) will be stored with the same sequence 57 // as they represented in 'format' but there will be specified verb's types also. 58 // Moreover their common length will be calculated and stored to decrease 59 // destination []byte buffer reallocations (just allocate big buffer, 60 // at least as more as 'formatParts' required). 61 formatParts []_CICE_FormatPart 62 63 colorMap map[Level]string // map of default colors for each level 64 colorMapMax int // max used len of ASCII color encoded seq. 65 66 // Sum of: len of just text parts + predicted len of log's parts. 67 minimumBufferLen int 68 69 ff _CICE_FieldsFormat // parts of fields formatting 70 bf _CICE_BodyFormat // parts of body formatting 71 cf _CICE_CallerFormat // parts of caller, stacktrace's frames formatting 72 sf _CICE_StacktraceFormat // parts of stacktrace formatting 73 ef _CICE_ErrorFormat // parts of attached error formatting 74 75 preEncodedFields []byte 76 preEncodedFieldsWritten int16 77 } 78 ) 79 80 var ( 81 // Make sure we won't break API. 82 _ CI_Encoder = (*CI_ConsoleEncoder)(nil) 83 ) 84 85 // SetFormat allows you to set format string that will be used as a template 86 // to generate and represent all log Entry objects. 87 // 88 // Calling this method many times will overwrite previous value of format string. 89 // 90 // The format string will be parsed and applied only after CI_ConsoleEncoder 91 // is registered with CommonIntegrator using CommonIntegrator.WithEncoder() method. 92 // After it is done, the format string cannot be changed and this method has no-op. 93 // 94 // ----- 95 // 96 // Attached ekaerr.Error. 97 // 98 // You know that you may log ekaerr.Error objects using special log finishers, 99 // like Logger.Errore(), Logger.Warne(), etc. 100 // According the main rule and purpose of ekaerr packages, 101 // the attached fields are associated with its stackframe. 102 // So, fields of a some stackframe will be placed exactly near related stackframe. 103 // You can't write them all at the one place. 104 // Read more below about verbs to recognize how you can manage the output. 105 // 106 // ----- 107 // 108 // Verbs. Introduction. 109 // 110 // The format string has its own verbs and verbs' parameters using which 111 // you can manage the place and view of Entry's parts. 112 // 113 // All verbs has the following format: 114 // "{{<verb_name>/<verb_parameter_1>/.../<verb_parameter_N>}}" 115 // 116 // You should build your own format string using verbs, separators, text, etc. 117 // 118 // For each verb there's verb name and its short aliases, so you can use any of that. 119 // Each verb has its own parameters and its defaults. 120 // You can overwrite one, two, N or all default verb parameters. 121 // 122 // The order of verb parameters doesn't matter. 123 // Letter case of verb names or its parameter names doesn't matter. 124 // 125 // Below you can see the verbs and its parameters. 126 // Keep in mind, that each verb's parameter has its own key, 127 // that determines what this verb parameter is. 128 // 129 // Verbs. List. 130 // 131 // 0. Empty verb / incorrect verb. 132 // All text that is not verb or an incorrect verb will be used as is. 133 // It will be a part of final log message. 134 // 135 // 1. Entry.Level verb. 136 // Names: "level", "lvl", "l". 137 // 138 // The verb will be replaced by Entry.Level string representation. 139 // By default it writes a full Level's capitalized name. 140 // The same as is returned by Level.String(). 141 // 142 // Parameters: 143 // (Only first parameter is supported. 2nd and next will be ignored. 144 // Incorrect parameter will be ignored.) 145 // 146 // - "d", "D": Write Level's number instead of string. 147 // From "0" for LEVEL_EMERGENCY down to "7" for LEVEL_DEBUG. 148 // - "s": Write Level's short string variant. 149 // The same as is returned by Level.String3(). 150 // Examples: "Emerg", "Ale", "Cri", "Err", "War", "Noe", "Inf", "Deb". 151 // - "S": Write Level's short upper-cased string variant. 152 // The same as is returned by Level.ToUpper3(). 153 // Examples: "EMERG", "ALE", "CRI", "ERR", "WAR", "NOE", "INF", "DEB". 154 // - "ss": Write Level's full string variant. 155 // This is default verb parameter. The same as is returned by Level.String(). 156 // - "SS": Write Level's full upper-cased string variant. 157 // The same as is returned by Level.ToUpper(). 158 // Examples: "EMERGENCY", "ALERT", "CRITICAL", "WARNING", etc. 159 // 160 // 2. Entry.Time verb. 161 // Names: "time", "t". 162 // 163 // The verb will be replaced by Entry.Time string representation. 164 // Default time format is: "Mon, Jan 02 15:04:05". 165 // 166 // Parameters: 167 // (Only first parameter is supported. 2nd and next will be ignored. 168 // Incorrect parameter will be ignored.) 169 // 170 // - "UNIX", "TIMESTAMP": Write time.Time as unix timestamp in seconds. 171 // Example: 1619549194 for Tue Apr 27 2021 18:46:34 GMT+0000 172 // - "ANSIC": Write time.Time as time.ANSIC format. 173 // Example: "Mon Jan _2 15:04:05 2006". 174 // - "UNIXDATE", "UNIX_DATE": Write time.Time as time.UnixDate format. 175 // Example: "Mon Jan _2 15:04:05 MST 2006". 176 // - "RUBYDATE", "RUBY_DATE": Write time.Time as time.RubyDate format. 177 // Example: "Mon Jan 02 15:04:05 -0700 2006". 178 // - "RFC822": Write time.Time as time.RFC822 format. 179 // Example: "02 Jan 06 15:04 MST". 180 // - "RFC822Z": Write time.Time as time.RFC822Z format. 181 // Example: "02 Jan 06 15:04 -0700". 182 // - "RFC850": Write time.Time as time.RFC850 format. 183 // Example: "Monday, 02-Jan-06 15:04:05 MST". 184 // - "RFC1123": Write time.Time as time.RFC1123 format. 185 // Example: "Mon, 02 Jan 2006 15:04:05 MST". 186 // - "RFC1123Z": Write time.Time as time.RFC1123Z format. 187 // Example: "Mon, 02 Jan 2006 15:04:05 -0700". 188 // - "RFC3339": Write time.Time as time.RFC3339 format. 189 // Example: "2006-01-02T15:04:05Z07:00". 190 // - "<your_own_time_format>: Uses string as time format. 191 // time.Time.Format() will be called with that format string. 192 // 193 // 3. Entry's log body verb. 194 // Names: "message", "body", "m", "b". 195 // 196 // The verb will be replaced by Entry's log message. 197 // You can include this verb only once. 198 // 2nd and next these verbs will be treated as just a text. 199 // 200 // Parameters: 201 // (The same key parameters will be overwritten by the last one. 202 // The parsing of parameters will be stopped when an incorrect parameter found). 203 // 204 // - "?^<text>": Places <text> before log's body if body is not empty. 205 // - "?$<text>": Places <text> after log's body if body is not empty. 206 // 207 // 4. Entry's caller verb. 208 // Names: "caller", "who", "w". 209 // 210 // The verb will be replaced by caller's info. 211 // It may include a package, function, line number of the caller. 212 // The caller is the function that calls a log finisher. 213 // 214 // Parameters: 215 // (The same key parameters will be overwritten by the last one. 216 // The parsing of parameters will be stopped when an incorrect parameter found). 217 // 218 // - "0": Do not include caller's info. 219 // It's meaningless by the first view, but the main purpose of that verb is 220 // the fact that you can specify format using "f<format>" verb parameter, 221 // that also will be used as a stacktrace's format. 222 // So, if you want to specify format for stacktrace, 223 // but don't want to include caller's info you need this verb's parameter. 224 // - "f<format>", "F<format>": You can specify format of caller string, 225 // that will be used as template to generate both of caller's info and stacktrace. 226 // <format> string must be combined by your own way with: 227 // - "w": Short function name. Only function, without package path. 228 // - "W": Full function name. Includes package path. 229 // - "f": Short filename. Only filename, without full path to that file. 230 // - "F": Full filename. Includes full path to that file. 231 // - "l", "L": File's line number. 232 // - "p", "P": Full package path. 233 // - <any_other>: Writes as is. Useful to split format's parts. 234 // 235 // Example: "{{w/0/fF:L}}" is a caller's verb, that specifies format 236 // "<full_file_name>:<line_number>" for both of caller and stacktrace, 237 // but caller info won't be included to the log's output. 238 // 239 // 5. Entry's stacktrace verb. 240 // Names: "stacktrace", "s". 241 // 242 // The verb will be replaced by stacktrace, if it's presented. 243 // Each stack frame may include its package, function, line number. 244 // 245 // WARNING. 246 // If you won't add this verb, the fields of your attached ekaerr.Error 247 // won't be encoded, because they are heavily linked with stacktrace! 248 // 249 // Parameters: 250 // (The same key parameters will be overwritten by the last one. 251 // The parsing of parameters will be stopped when an incorrect parameter found). 252 // 253 // - "?^<text>": Places <text> before stacktrace if stacktrace is presented. 254 // - "?$<text>": Places <text> after stacktrace if stacktrace is presented. 255 // 256 // 6. Entry's fields verb. 257 // Names: "fields", "f". 258 // 259 // The verb will be replaced by log Entry's fields. 260 // Keep in mind, this verb has an affect only for log's fields. 261 // If you have attached ekaerr.Error, its fields will be printed if 262 // stacktrace's verb is included. But this verb has an affect of format 263 // to that fields also. 264 // 265 // Parameters: 266 // (The same key parameters will be overwritten by the last one. 267 // The parsing of parameters will be stopped when an incorrect parameter found). 268 // 269 // - "?^<text>": Places <text> before FIRST field (in stackframe). 270 // - "?$<text>": Places <text> after LAST field (in stackframe). 271 // - "k<text>": Places <text> before EACH field's key. 272 // - "v<text>": Places <text> before EACH field's value. 273 // - "e<text>": Places <text> after EACH field's value (last field excluded). 274 // - "l<text>": Places <text> at the each new line of log Entry's fields part. 275 // - "le<text>": Places <text> at the each new line of attached ekaerr.Error fields part. 276 // - "*<number>": <number> is how much fields are placed at the one line. 277 // (By default: 4. Use <= 0 value to place all fields at the one line). 278 // 279 // 7. TTY coloring verb. 280 // Names: "color", "c". 281 // 282 // This verb allows you to make some parts of final messages be colored. 283 // Color in a TTY terms is just a special escape sequence. 284 // Think about this verb as a HTML tag. 285 // 286 // First of all, if you're placing this verb of starting coloring, 287 // you need to place the verb of ending coloring. 288 // Otherwise all next data will be colored too until next color verb is found. 289 // It's not about CI_ConsoleEncoder but about how TTY coloring works. 290 // 291 // Next thing you need to know is 292 // There's "default" colors for each log's Level. 293 // If you don't present a color using verb's parameters, 294 // the default log's level color will be used. 295 // You can overwrite default colors for level using SetColorFor() method. 296 // 297 // How parameters works. 298 // All parameters are split to its groups: 299 // - Bold (enable/disable), 300 // - Italic (enable/disable), 301 // - Underline (enable/disable), 302 // - Background color (overwrite/disable), 303 // - Foreground color (overwrite/disable), 304 // - Cancel any color. 305 // You may specify more than 1 parameter to combine them. 306 // If you specify more then 1 parameter for the same group, the last one will be used. 307 // If you specify "cancel any color" parameter, all others parameters will be ignored. 308 // If any parameter is invalid, all others will be ignored. 309 // 310 // To specify color, you can use: 311 // - ASCII colors: Use "ascii<color>" or "ascii(<color>)" syntax. 312 // Allowable colors: [30..37]+[90..97] for foreground, 313 // [40..47]+[100..107] for background. 314 // Read more: https://en.wikipedia.org/wiki/ANSI_escape_code 315 // It's UB to specify foreground color for background and vice-versa. 316 // (Most likely ASCII affiliation of color will be used). 317 // - X256 colors: Use "<color>" syntax. Allowable colors: [0..255]. 318 // Read more using the link above. 319 // - HEX colors: Use "#<color>" format. Will be transformed to X256 colors. 320 // Read more: https://en.wikipedia.org/wiki/Web_colors 321 // - RGB colors: Use "rgb:<red>,<green>,<blue>" or "rgb(<red>,<green>,<blue>)" 322 // or "rgb,<red>,<green>,<blue>" syntax. 323 // All of <red>, <green>, <blue> must be in range [0..255]. 324 // Will be transformed to X256 colors. 325 // Read more using link above. 326 // - "-1": Disable coloring for desired type (background/foreground). 327 // 328 // Parameters: 329 // 330 // - No parameters: Uses log's Level default color. 331 // - "0": Disables all TTY font effects: color, bold, italic, underline. 332 // - "bold", "b": Use bold font. 333 // - "nobold", "nob": Disable bold font, return back to normal. 334 // - "italic", "i": Use italic font. 335 // - "noitalic", "noi": Disable italic font, return back to normal. 336 // - "underline", "u": Use underline font. 337 // - "nounderline", "nou": Disable underline font, return back to normal. 338 // - "fg:<color>: Set <color> for text's foreground. See above about colors. 339 // - "bg:<color>": Set <color> for text's background. See above about colors. 340 // 341 // Reminder. 342 // Make sure your terminal supports X256 colors or use ASCII colors otherwise. 343 // If your terminal doesn't support X256 colors and you will try to use it, 344 // you may get an ugly escape sequences in your output. 345 // 346 // Dropping colors for specific io.Writer. 347 // You may want to disable coloring for specific io.Writer leaving it for another. 348 // See CICE_DropColors() for more details. 349 // 350 // ----- 351 // 352 // If you won't set any format string, the default one will be used. 353 // It is: 354 // 355 // "{{c}}{{l}} {{t}}{{c/0}}\n" + // include colored level, colored time 356 // "{{m/?$\n}}" + // include message with \n if non-empty 357 // "{{f/?$\n/v = /e, /l\t/le\t\t}}" + // include fields with " = " as key-value separator 358 // "{{s/?$\n/e, }}" + // include stacktrace with \n if non-empty 359 // "{{w/0/fd}}" + // omit caller, specify each stacktrace's frame format 360 // "\n" 361 // 362 func (ce *CI_ConsoleEncoder) SetFormat(newFormat string) *CI_ConsoleEncoder { 363 364 if s := strings.TrimSpace(newFormat); s == "" { 365 // Just check whether 'newFormat' is non-empty string or a string 366 // that contains not only whitespace characters. But do not ignore them. 367 return ce 368 } 369 370 ce.format = newFormat 371 return ce 372 } 373 374 // SetColorFor sets color what will be used as a replace for level-depended 375 // color verb from the 'format' string that is set by SetFormat() func 376 // 377 // Example: "c/fg:ascii:31/b", "c/fg:#123456/bg:rgb,50,50,50/i/u". 378 func (ce *CI_ConsoleEncoder) SetColorFor(level Level, color string) *CI_ConsoleEncoder { 379 380 if ce.colorMap == nil { 381 ce.colorMap = make(map[Level]string) 382 } 383 384 if encodedColor := ce.rvColorHelper(color); encodedColor != "" { 385 ce.colorMap[level] = encodedColor 386 if l := len(encodedColor); ce.colorMapMax < l { 387 ce.colorMapMax = l 388 } 389 } 390 391 return ce 392 } 393 394 // PreEncodeField allows you to pre-encode some ekaletter.LetterField, 395 // that is must be used with EACH Entry that will be encoded using this CI_ConsoleEncoder. 396 // 397 // It's useful when you want some unchanged runtime data for each log message, 398 // like git hash commit, version, etc. Or if you want to create many loggers 399 // attach some different fields to them and log different logs using them. 400 // 401 // Unnamed fields are not allowed. 402 // 403 // WARNING! 404 // PreEncodeField() MUST BE USED ONLY IF CI_ConsoleEncoder HAS BEEN REGISTERED 405 // WITH SOME CommonIntegrator ALREADY. UB OTHERWISE, MAY PANIC! 406 func (ce *CI_ConsoleEncoder) PreEncodeField(f ekaletter.LetterField) { 407 408 // Avoid calls of PreEncodeField() when CI_ConsoleEncoder has not built yet. 409 if f.Key == "" || len(ce.formatParts) == 0 || f.IsInvalid() || 410 f.RemoveVary() && f.IsZero() { 411 return 412 } 413 414 preEncodedFieldsLenBak := len(ce.preEncodedFields) 415 ce.preEncodedFields = ce.encodeField(ce.preEncodedFields, f, false, ce.preEncodedFieldsWritten) 416 417 if len(ce.preEncodedFields) != preEncodedFieldsLenBak { 418 ce.preEncodedFieldsWritten++ 419 } 420 } 421 422 // EncodeEntry encodes passed Entry as text using provided (and parsed) 423 // format string that is set by SetFormat() method, returning a RAW encoded data. 424 // 425 // It may includes a special ASCII escape sequences to color output. 426 // It's enabled by default (if you didn't change format string). 427 // 428 // EncodeEntry is for internal purposes only and MUST NOT be called directly. 429 // UB otherwise, may panic. 430 func (ce *CI_ConsoleEncoder) EncodeEntry(e *Entry) []byte { 431 432 // TODO: Reuse allocated buffers 433 434 to := make([]byte, 0, ce.minimumBufferLen) 435 436 // Use last ekaerr.Error's message as Entry's one if it's empty. 437 if e.ErrLetter != nil { 438 if l := len(e.ErrLetter.Messages); l > 0 && e.LogLetter.Messages[0].Body == "" { 439 e.LogLetter.Messages[0].Body = e.ErrLetter.Messages[l-1].Body 440 e.ErrLetter.Messages[l-1].Body = "" 441 } 442 } 443 444 for _, part := range ce.formatParts { 445 switch part.typ.Type() { 446 447 case _CICE_FPT_VERB_JUST_TEXT: 448 to = ce.encodeJustText(to, part) 449 case _CICE_FPT_VERB_COLOR_CUSTOM: 450 to = ce.encodeColor(to, part) 451 case _CICE_FPT_VERB_COLOR_FOR_LEVEL: 452 to = ce.encodeColorForLevel(to, e) 453 case _CICE_FPT_VERB_BODY: 454 to = ce.encodeBody(to, e) 455 case _CICE_FPT_VERB_TIME: 456 to = ce.encodeTime(e, part, to) 457 case _CICE_FPT_VERB_LEVEL: 458 to = ce.encodeLevel(to, part, e) 459 case _CICE_FPT_VERB_STACKTRACE: 460 to = ce.encodeStacktrace(to, e) 461 case _CICE_FPT_VERB_CALLER: 462 to = ce.encodeCaller(to, e) 463 464 case _CICE_FPT_VERB_FIELDS: 465 errLetterSystemFields := []ekaletter.LetterField(nil) 466 if e.ErrLetter != nil { 467 errLetterSystemFields = e.ErrLetter.SystemFields 468 } 469 to = ce.encodeFields(to, e.LogLetter.SystemFields, errLetterSystemFields, false, false) 470 471 // Handle special case when ekaerr.Error's ekaletter.Letter has a fields 472 // but has no stacktrace. It means that lightweight error has been created. 473 lightweightErrorFields := []ekaletter.LetterField(nil) 474 if e.ErrLetter != nil && len(e.ErrLetter.StackTrace) == 0 && len(e.ErrLetter.Fields) > 0 { 475 lightweightErrorFields = e.ErrLetter.Fields 476 } 477 to = ce.encodeFields(to, e.LogLetter.Fields, lightweightErrorFields, false, true) 478 } 479 } 480 481 // Restore ekaerr.Error's last message that was used as Entry's message. 482 if e.ErrLetter != nil { 483 if l := len(e.ErrLetter.Messages); l > 0 && e.ErrLetter.Messages[l-1].Body == "" { 484 e.ErrLetter.Messages[l-1].Body = e.LogLetter.Messages[0].Body 485 e.LogLetter.Messages[0].Body = "" 486 } 487 } 488 489 return to 490 } 491 492 // CICE_DropColors returns an io.Writer, you can write a data with TTY colors to, 493 // that will rewrite everything but colors to the your dest io.Writer. 494 // 495 // You can pass returned io.Writer to the CommonIntegrator.WriteTo() 496 // method associating it with the CI_ConsoleEncoder that writes a log messages 497 // with a TTY colors. Returned io.Writer will rewrite message to your io.Writer 498 // but without these shell color sequences. 499 // 500 // It's useful when you want to write colored log messages to TTY but 501 // raw to the files. Or any other destination. 502 // 503 // Keep in mind, it's not "free" operation. It allocates RAM buffer, 504 // writes raw data to, parses it, droppings colors and rewrites cleared raw data. 505 func CICE_DropColors(dest io.Writer) io.Writer { 506 return &_CICE_DropColors{dest: dest} 507 }