mirror of
https://github.com/gechandesu/structlog.git
synced 2026-01-24 00:14:12 +03:00
init
This commit is contained in:
8
.editorconfig
Normal file
8
.editorconfig
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.v]
|
||||||
|
indent_style = tab
|
||||||
8
.gitattributes
vendored
Normal file
8
.gitattributes
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
* text=auto eol=lf
|
||||||
|
*.bat eol=crlf
|
||||||
|
|
||||||
|
*.v linguist-language=V
|
||||||
|
*.vv linguist-language=V
|
||||||
|
*.vsh linguist-language=V
|
||||||
|
v.mod linguist-language=V
|
||||||
|
.vdocignore linguist-language=ignore
|
||||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Binaries for programs and plugins
|
||||||
|
main
|
||||||
|
structlog
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# Ignore binary output folders
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# Ignore common editor/system specific metadata
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
|
||||||
|
# ENV
|
||||||
|
.env
|
||||||
|
|
||||||
|
# vweb and database
|
||||||
|
*.db
|
||||||
|
*.js
|
||||||
1
.vdocignore
Normal file
1
.vdocignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
examples/
|
||||||
18
LICENSE
Normal file
18
LICENSE
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 gechandesu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||||
|
following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
||||||
|
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||||
|
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
41
README.md
Normal file
41
README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Structured Logs
|
||||||
|
|
||||||
|
The `structlog` module develops the idea of [vlogger](https://github.com/CG-SS/vlogger)
|
||||||
|
by constructing a record using a chain of method calls.
|
||||||
|
|
||||||
|
## Concept
|
||||||
|
|
||||||
|
When initialized, the logger starts a thread with a record handler. The logger
|
||||||
|
has a number of methods, each of which creates a record with the corresponding
|
||||||
|
logging level, e.g. `info()`.
|
||||||
|
|
||||||
|
By chaining method calls, the module user can create a record with any structure.
|
||||||
|
The final `.send()` call sends the record to the handler for writing.
|
||||||
|
|
||||||
|
The record handler completely defines how to prepare the `Record` object for
|
||||||
|
writing, how and whereto the writing will occur. The handler must implement the
|
||||||
|
`RecordHandler` interface. Two ready-made handlers for recording are provided:
|
||||||
|
`TextHandler` (the default) and `JSONHandler` for JSON formatted logs.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```v
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
log := structlog.new()
|
||||||
|
defer {
|
||||||
|
log.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info().message('Hello, World!').send()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
2026-01-03T09:33:35.366Z [INFO ] message: 'Hello, World!'
|
||||||
|
```
|
||||||
|
|
||||||
|
See also [examples](examples/) dir for more usage examples.
|
||||||
27
examples/basic.v
Normal file
27
examples/basic.v
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import rand
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logger with default configuratuion.
|
||||||
|
log := structlog.new()
|
||||||
|
defer {
|
||||||
|
// Since processing and writing the log is done in a separate thread,
|
||||||
|
// we need to wait for it to complete before exiting the program.
|
||||||
|
log.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write some logs.
|
||||||
|
//
|
||||||
|
// Note the call chain. First, the info() call creates a empty `structlog.Record`
|
||||||
|
// object with `info` log level. The next message() call adds a message field with
|
||||||
|
// the specified text to the record. The final send() call sends the record to the
|
||||||
|
// record handler (TextHandler by default) which writes log to stardard output.
|
||||||
|
log.info().message('Hello, World!').send()
|
||||||
|
|
||||||
|
// You can set your own named fields.
|
||||||
|
log.info().field('random_string', rand.string(5)).send()
|
||||||
|
log.info().field('answer', 42).field('computed_by', 'Deep Thought').send()
|
||||||
|
|
||||||
|
// Errors can be passed to logger as is.
|
||||||
|
log.error().message('this line contains error').error(error('oops')).send()
|
||||||
|
}
|
||||||
21
examples/json_log.v
Normal file
21
examples/json_log.v
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
import rand
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logger with JSONHandler.
|
||||||
|
log := structlog.new(
|
||||||
|
level: .trace
|
||||||
|
handler: structlog.JSONHandler{
|
||||||
|
writer: os.stdout()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
defer {
|
||||||
|
log.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info().message('Hello, World!').send()
|
||||||
|
log.info().field('random_string', rand.string(5)).send()
|
||||||
|
log.info().field('answer', 42).field('computed_by', 'Deep Thought').send()
|
||||||
|
log.error().message('this line contains error').error(error('oops')).send()
|
||||||
|
}
|
||||||
16
examples/logging_levels.v
Normal file
16
examples/logging_levels.v
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import structlog
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logger with non-default logging level.
|
||||||
|
log := structlog.new(level: .trace) // try to change logging level
|
||||||
|
defer {
|
||||||
|
log.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.trace().message('hello trace').send()
|
||||||
|
log.debug().message('hello debug').send()
|
||||||
|
log.info().message('hello info').send()
|
||||||
|
log.warn().message('hello warn').send()
|
||||||
|
log.error().message('hello error').send()
|
||||||
|
log.fatal().message('hello fatal').send() // on fatal program exits immediately with exit code 1
|
||||||
|
}
|
||||||
17
examples/unix_timestamp.v
Normal file
17
examples/unix_timestamp.v
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import os
|
||||||
|
import structlog
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logger with edited timestamp.
|
||||||
|
log := structlog.new(
|
||||||
|
timestamp_format: .unix
|
||||||
|
handler: structlog.JSONHandler{
|
||||||
|
writer: os.stdout()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
defer {
|
||||||
|
log.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info().message('Hello, World!').send()
|
||||||
|
}
|
||||||
435
structlog.v
Normal file
435
structlog.v
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
module structlog
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import strings
|
||||||
|
import time
|
||||||
|
import term
|
||||||
|
import x.json2 as json
|
||||||
|
|
||||||
|
pub interface RecordHandler {
|
||||||
|
mut:
|
||||||
|
// handle method must prepare the Record for writing and write it.
|
||||||
|
handle(rec Record) !
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Level {
|
||||||
|
none // disables all logs.
|
||||||
|
fatal // disables error, warn, info, debug and trace.
|
||||||
|
error // disables warn, info, debug and trace.
|
||||||
|
warn // disables info, debug and trace.
|
||||||
|
info // disables debug and trace.
|
||||||
|
debug // disables trace.
|
||||||
|
trace
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Value = i8
|
||||||
|
| i16
|
||||||
|
| i32
|
||||||
|
| i64
|
||||||
|
| int
|
||||||
|
| isize
|
||||||
|
| u8
|
||||||
|
| u16
|
||||||
|
| u32
|
||||||
|
| u64
|
||||||
|
| usize
|
||||||
|
| f32
|
||||||
|
| f64
|
||||||
|
| string
|
||||||
|
| bool
|
||||||
|
| []Value
|
||||||
|
| map[string]Value
|
||||||
|
|
||||||
|
// str returns a string representation of Value.
|
||||||
|
pub fn (v Value) str() string {
|
||||||
|
return match v {
|
||||||
|
i8 { v.str() }
|
||||||
|
i16 { v.str() }
|
||||||
|
i32 { v.str() }
|
||||||
|
i64 { v.str() }
|
||||||
|
int { v.str() }
|
||||||
|
isize { v.str() }
|
||||||
|
u8 { v.str() }
|
||||||
|
u16 { v.str() }
|
||||||
|
u32 { v.str() }
|
||||||
|
u64 { v.str() }
|
||||||
|
usize { v.str() }
|
||||||
|
f32 { v.str() }
|
||||||
|
f64 { v.str() }
|
||||||
|
string { v.str() }
|
||||||
|
bool { v.str() }
|
||||||
|
[]Value { v.str() }
|
||||||
|
map[string]Value { v.str() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field represents a named field of log record.
|
||||||
|
pub struct Field {
|
||||||
|
pub:
|
||||||
|
name string
|
||||||
|
value Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// as_map converts array of fields into map.
|
||||||
|
pub fn (f []Field) as_map() map[string]Value {
|
||||||
|
mut mapping := map[string]Value{}
|
||||||
|
for field in f {
|
||||||
|
mapping[field.name] = field.value
|
||||||
|
}
|
||||||
|
return mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
@[noinit]
|
||||||
|
pub struct Record {
|
||||||
|
channel chan Record
|
||||||
|
pub:
|
||||||
|
level Level
|
||||||
|
fields []Field
|
||||||
|
}
|
||||||
|
|
||||||
|
// append adds new fields to a record and returns the modified record.
|
||||||
|
pub fn (r Record) append(field ...Field) Record {
|
||||||
|
if field.len == 0 {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
mut fields_orig := unsafe { r.fields }
|
||||||
|
fields_orig << field
|
||||||
|
return Record{
|
||||||
|
channel: r.channel
|
||||||
|
level: r.level
|
||||||
|
fields: &fields_orig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepend adds new fields to the beginning of the record and returns the modified record.
|
||||||
|
pub fn (r Record) prepend(field ...Field) Record {
|
||||||
|
if field.len == 0 {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
mut new_fields := unsafe { field }
|
||||||
|
new_fields << r.fields
|
||||||
|
return Record{
|
||||||
|
channel: r.channel
|
||||||
|
level: r.level
|
||||||
|
fields: new_fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// field adds new field with given name and value to a record and returns the modified record.
|
||||||
|
pub fn (r Record) field(name string, value Value) Record {
|
||||||
|
return r.append(Field{ name: name, value: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
// field adds new message field to a record and returns the modified record.
|
||||||
|
// This is a shothand for `field('message', 'message text')`.
|
||||||
|
pub fn (r Record) message(s string) Record {
|
||||||
|
return r.field('message', s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// error adds an error as new field to a record and returns the modified record.
|
||||||
|
// The IError .msg() and .code() methods output will be logged.
|
||||||
|
pub fn (r Record) error(err IError) Record {
|
||||||
|
return r.append(Field{
|
||||||
|
name: 'error'
|
||||||
|
value: {
|
||||||
|
'msg': Value(err.msg())
|
||||||
|
'code': Value(err.code())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// send sends a record to the record handler for the futher processing and writing.
|
||||||
|
pub fn (r Record) send() {
|
||||||
|
r.channel <- r
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TimestampFormat {
|
||||||
|
default
|
||||||
|
rfc3339
|
||||||
|
rfc3339_micro
|
||||||
|
rfc3339_nano
|
||||||
|
ss
|
||||||
|
ss_micro
|
||||||
|
ss_milli
|
||||||
|
ss_nano
|
||||||
|
unix
|
||||||
|
unix_micro
|
||||||
|
unix_milli
|
||||||
|
unix_nano
|
||||||
|
custom
|
||||||
|
}
|
||||||
|
|
||||||
|
@[params]
|
||||||
|
pub struct LogConfig {
|
||||||
|
pub:
|
||||||
|
// level holds a logging level for the logger.
|
||||||
|
// This value cannot be changed after logger initialization.
|
||||||
|
level Level = .info
|
||||||
|
|
||||||
|
add_level bool = true // if true add `level` field to all log records.
|
||||||
|
add_timestamp bool = true // if true add `timestamp` field to all log records.
|
||||||
|
|
||||||
|
// timestamp_format sets the format of datettime for logs.
|
||||||
|
// TimestampFormat values map 1-to-1 to the date formats provided by `time.Time`.
|
||||||
|
// If .custom format is selected the timestamp_custom field must be set.
|
||||||
|
timestamp_format TimestampFormat = .rfc3339
|
||||||
|
|
||||||
|
// timestamp_custom sets the custom datetime string format if timestapm_format is
|
||||||
|
// set to .custom. See docs for Time.format_custom() fn from stadnard `time` module.
|
||||||
|
timestamp_custom string
|
||||||
|
|
||||||
|
// If timestamp_local is true the local time will be used instead of UTC.
|
||||||
|
timestamp_local bool
|
||||||
|
|
||||||
|
// handler holds a log record handler object which is used to process logs.
|
||||||
|
handler RecordHandler = TextHandler{
|
||||||
|
writer: os.stdout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timestamp(format TimestampFormat, custom string, local bool) Value {
|
||||||
|
mut t := time.utc()
|
||||||
|
if local {
|
||||||
|
t = t.local()
|
||||||
|
}
|
||||||
|
return match format {
|
||||||
|
.default { t.format() }
|
||||||
|
.rfc3339 { t.format_rfc3339() }
|
||||||
|
.rfc3339_micro { t.format_rfc3339_micro() }
|
||||||
|
.rfc3339_nano { t.format_rfc3339_nano() }
|
||||||
|
.ss { t.format_ss() }
|
||||||
|
.ss_micro { t.format_ss_micro() }
|
||||||
|
.ss_milli { t.format_ss_milli() }
|
||||||
|
.ss_nano { t.format_ss_nano() }
|
||||||
|
.unix { t.unix() }
|
||||||
|
.unix_micro { t.unix_micro() }
|
||||||
|
.unix_milli { t.unix_milli() }
|
||||||
|
.unix_nano { t.unix_nano() }
|
||||||
|
.custom { t.custom_format(custom) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// new creates new logger with given config. See LogConfig for defaults.
|
||||||
|
// This function starts a separate thread for processing and writing logs.
|
||||||
|
// The calling code MUST wait for this thread to complete to ensure all logs
|
||||||
|
// are written correctly. To do this, close the logger as shown in the examples.
|
||||||
|
// Example:
|
||||||
|
// ```v ignore
|
||||||
|
// log := structlog.new()
|
||||||
|
// defer {
|
||||||
|
// log.close()
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
pub fn new(config LogConfig) StructuredLog {
|
||||||
|
ch := chan Record{cap: 4096}
|
||||||
|
|
||||||
|
mut logger := StructuredLog{
|
||||||
|
LogConfig: config
|
||||||
|
channel: ch
|
||||||
|
}
|
||||||
|
|
||||||
|
handler_thread := go fn [mut logger] () {
|
||||||
|
loop: for {
|
||||||
|
mut rec := <-logger.channel or { break }
|
||||||
|
|
||||||
|
if int(rec.level) > int(logger.level) {
|
||||||
|
continue loop
|
||||||
|
}
|
||||||
|
|
||||||
|
mut extra_fields := []Field{}
|
||||||
|
|
||||||
|
if logger.add_timestamp {
|
||||||
|
extra_fields << Field{
|
||||||
|
name: 'timestamp'
|
||||||
|
value: timestamp(logger.timestamp_format, logger.timestamp_custom,
|
||||||
|
logger.timestamp_local)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger.add_level {
|
||||||
|
extra_fields << Field{
|
||||||
|
name: 'level'
|
||||||
|
value: rec.level.str()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rec = rec.prepend(...extra_fields)
|
||||||
|
|
||||||
|
mut handler := logger.handler
|
||||||
|
handler.handle(rec) or { eprintln('error when handling log record!') }
|
||||||
|
|
||||||
|
if rec.level == .fatal {
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
logger.handler_thread = handler_thread
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
@[heap; noinit]
|
||||||
|
pub struct StructuredLog {
|
||||||
|
LogConfig
|
||||||
|
mut:
|
||||||
|
channel chan Record
|
||||||
|
handler_thread thread
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (s StructuredLog) record(level Level) Record {
|
||||||
|
return Record{
|
||||||
|
channel: s.channel
|
||||||
|
level: level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trace creates new log record with trace level.
|
||||||
|
pub fn (s StructuredLog) trace() Record {
|
||||||
|
return s.record(.trace)
|
||||||
|
}
|
||||||
|
|
||||||
|
// debug creates new log record with debug level.
|
||||||
|
pub fn (s StructuredLog) debug() Record {
|
||||||
|
return s.record(.debug)
|
||||||
|
}
|
||||||
|
|
||||||
|
// info creates new log record with info level.
|
||||||
|
pub fn (s StructuredLog) info() Record {
|
||||||
|
return s.record(.info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// warn creates new log record wth warning level.
|
||||||
|
pub fn (s StructuredLog) warn() Record {
|
||||||
|
return s.record(.warn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// error creates new log record with error level.
|
||||||
|
pub fn (s StructuredLog) error() Record {
|
||||||
|
return s.record(.error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fatal creates new log record with fatal level.
|
||||||
|
// Note: After calling `send()` on record with fatal level the program will
|
||||||
|
// immediately exit with exit code 1.
|
||||||
|
pub fn (s StructuredLog) fatal() Record {
|
||||||
|
return s.record(.fatal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// close closes the internal communication channell (which is used for transfer
|
||||||
|
// log messages) and waits for record handler thread. It MUST be called for
|
||||||
|
// normal log processing.
|
||||||
|
pub fn (s StructuredLog) close() {
|
||||||
|
s.channel.close()
|
||||||
|
s.handler_thread.wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultHandler is a default empty implementation of RecordHandler interface.
|
||||||
|
// Its only purpose for existence is to be embedded in a concrete implementation
|
||||||
|
// of the interface for common struct fields.
|
||||||
|
pub struct DefaultHandler {
|
||||||
|
pub mut:
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle is the default implementation of handle method of RecordHandler. It does nothing.
|
||||||
|
pub fn (mut h DefaultHandler) handle(rec Record) ! {}
|
||||||
|
|
||||||
|
pub struct JSONHandler {
|
||||||
|
DefaultHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle converts the log record into json string and writes it into underlying writer.
|
||||||
|
pub fn (mut h JSONHandler) handle(rec Record) ! {
|
||||||
|
str := json.encode[map[string]Value](rec.fields.as_map()) + '\n'
|
||||||
|
h.writer.write(str.bytes())!
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TextHandler {
|
||||||
|
DefaultHandler
|
||||||
|
pub:
|
||||||
|
// If true use colors in log messages. Otherwise disable colors at all.
|
||||||
|
// Turning on/off color here does not affect any colors that may be contained
|
||||||
|
// within the log itself i.e. in-string ANSI escape sequences are not processed.
|
||||||
|
color bool = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle builds a log string from given record and writes it into underlying writer.
|
||||||
|
pub fn (mut h TextHandler) handle(rec Record) ! {
|
||||||
|
mut buf := strings.new_builder(512)
|
||||||
|
for i, field in rec.fields {
|
||||||
|
match field.name {
|
||||||
|
'timestamp' {
|
||||||
|
if field.value is string {
|
||||||
|
buf.write_string(field.value)
|
||||||
|
} else {
|
||||||
|
buf.write_string((field.value as i64).str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'level' {
|
||||||
|
mut lvl := ''
|
||||||
|
if h.color {
|
||||||
|
lvl = match rec.level {
|
||||||
|
.trace { term.magenta('TRACE') }
|
||||||
|
.debug { term.cyan('DEBUG') }
|
||||||
|
.info { term.white('INFO ') }
|
||||||
|
.warn { term.yellow('WARN ') }
|
||||||
|
.error { term.red('ERROR') }
|
||||||
|
.fatal { term.bg_red('FATAL') }
|
||||||
|
.none { '' }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lvl = match rec.level {
|
||||||
|
.trace { 'TRACE' }
|
||||||
|
.debug { 'DEBUG' }
|
||||||
|
.info { 'INFO ' }
|
||||||
|
.warn { 'WARN ' }
|
||||||
|
.error { 'ERROR' }
|
||||||
|
.fatal { 'FATAL' }
|
||||||
|
.none { '' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.write_byte(`[`)
|
||||||
|
buf.write_string(lvl)
|
||||||
|
buf.write_byte(`]`)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if field.value is map[string]Value {
|
||||||
|
mut j := 0
|
||||||
|
for k, v in field.value {
|
||||||
|
j++
|
||||||
|
buf.write_string('${field.name}.${k}')
|
||||||
|
buf.write_byte(`:`)
|
||||||
|
buf.write_byte(` `)
|
||||||
|
buf.write_string(quote(v.str()))
|
||||||
|
if j != field.value.len {
|
||||||
|
buf.write_byte(` `)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.write_string(field.name)
|
||||||
|
buf.write_byte(`:`)
|
||||||
|
buf.write_byte(` `)
|
||||||
|
buf.write_string(quote(field.value.str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i != rec.fields.len {
|
||||||
|
buf.write_byte(` `)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.write_byte(`\n`)
|
||||||
|
h.writer.write(buf)!
|
||||||
|
}
|
||||||
|
|
||||||
|
@[inline]
|
||||||
|
fn quote(input string) string {
|
||||||
|
if !input.contains(' ') {
|
||||||
|
return input
|
||||||
|
}
|
||||||
|
if input.contains("'") {
|
||||||
|
return '"' + input + '"'
|
||||||
|
}
|
||||||
|
return "'" + input + "'"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user