Skip to content

Commit b423c16

Browse files
committed
all: various improvements
* Add os.File.flush() call to fix writing log into files. * Add comma as TextHandler fields separator. * Add struct_adapter() * Add field() generic function to creating Field instances. * Add new write_to_file.v example. * Rename Record.field() to Record.add()
1 parent c6d5375 commit b423c16

6 files changed

Lines changed: 129 additions & 10 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ bin/
2222
# vweb and database
2323
*.db
2424
*.js
25+
26+
*.bak

examples/basic.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ fn main() {
1919
log.info().message('Hello, World!').send()
2020

2121
// You can set your own named fields.
22-
log.info().field('random_string', rand.string(5)).send()
23-
log.info().field('answer', 42).field('computed_by', 'Deep Thought').send()
22+
log.info().add('random_string', rand.string(100)).send()
23+
log.info().add('answer', 42).add('computed_by', 'Deep Thought').send()
2424

2525
// Errors can be passed to logger as is.
2626
log.error().message('this line contains error').error(error('oops')).send()

examples/json_log.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn main() {
1515
}
1616

1717
log.info().message('Hello, World!').send()
18-
log.info().field('random_string', rand.string(5)).send()
19-
log.info().field('answer', 42).field('computed_by', 'Deep Thought').send()
18+
log.info().add('random_string', rand.string(100)).send()
19+
log.info().add('answer', 42).add('computed_by', 'Deep Thought').send()
2020
log.error().message('this line contains error').error(error('oops')).send()
2121
}

examples/write_to_file.v

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import os
2+
import structlog
3+
4+
fn main() {
5+
// Open a file in append mode. If file does not exists it will be created.
6+
log_path := os.join_path_single(os.temp_dir(), 'example_log')
7+
log_file := os.open_file(log_path, 'a+') or {
8+
eprintln('Error: cound not open log file ${log_path}: ${err}')
9+
exit(1)
10+
}
11+
12+
eprintln('Log file location: ${log_path}')
13+
14+
// Initialize logger with os.File as writer.
15+
log := structlog.new(
16+
handler: structlog.TextHandler{
17+
color: false
18+
writer: log_file
19+
}
20+
)
21+
defer {
22+
log.close()
23+
}
24+
25+
log.info().message('Hello, World!').send()
26+
}

structlog.v

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,21 @@ pub fn (r Record) prepend(field ...Field) Record {
114114
}
115115
}
116116

117+
// add adds new field with given name and value to a record and returns the modified record.
118+
pub fn (r Record) add(name string, value Value) Record {
119+
return r.append(field(name, value))
120+
}
121+
117122
// field adds new field with given name and value to a record and returns the modified record.
123+
@[deprecated: 'use add() instead']
118124
pub fn (r Record) field(name string, value Value) Record {
119125
return r.append(Field{ name: name, value: value })
120126
}
121127

122128
// message adds new message field to a record and returns the modified record.
123-
// This is a shothand for `field('message', 'message text')`.
129+
// This is a shothand for `add('message', 'message text')`.
124130
pub fn (r Record) message(s string) Record {
125-
return r.field('message', s)
131+
return r.add('message', s)
126132
}
127133

128134
// error adds an error as new field to a record and returns the modified record.
@@ -351,6 +357,9 @@ pub struct JSONHandler {
351357
pub fn (mut h JSONHandler) handle(rec Record) ! {
352358
str := json.encode[map[string]Value](rec.fields.as_map()) + '\n'
353359
h.writer.write(str.bytes())!
360+
if h.writer is os.File {
361+
h.writer.flush()
362+
}
354363
}
355364

356365
pub struct TextHandler {
@@ -411,7 +420,7 @@ pub fn (mut h TextHandler) handle(rec Record) ! {
411420
buf.write_byte(` `)
412421
buf.write_string(quote(v.str()))
413422
if j != field.value.len {
414-
buf.write_byte(` `)
423+
buf.write_string(', ')
415424
}
416425
}
417426
} else {
@@ -422,12 +431,19 @@ pub fn (mut h TextHandler) handle(rec Record) ! {
422431
}
423432
}
424433
}
425-
if i != rec.fields.len {
426-
buf.write_byte(` `)
434+
if i + 1 != rec.fields.len {
435+
if i in [0, 1] {
436+
buf.write_byte(` `)
437+
} else {
438+
buf.write_string(', ')
439+
}
427440
}
428441
}
429442
buf.write_byte(`\n`)
430443
h.writer.write(buf)!
444+
if h.writer is os.File {
445+
h.writer.flush()
446+
}
431447
}
432448

433449
@[inline]
@@ -440,3 +456,78 @@ fn quote(input string) string {
440456
}
441457
return "'" + input + "'"
442458
}
459+
460+
// struct_adapter generates the log fields list form a flat struct.
461+
// Supported struct field attrubutes:
462+
//
463+
// | Attribute | Meaning |
464+
// | ------------------- | ------------------------------------------ |
465+
// | `@[skip]` | Do not process field at all |
466+
// | `@[structlog: '-']` | Do not process field at all |
467+
// | `@[omitempty]` | Do not process field if it has empty value |
468+
//
469+
// Note: Nested struct fields are not supported.
470+
pub fn struct_adapter[T](s T) []Field {
471+
$if T !is $struct {
472+
$compile_error('structlog.struct_adapted: only struct types is accepted')
473+
}
474+
mut fields := []Field{}
475+
mut skip := false
476+
mut omitempty := false
477+
$for f in s.fields {
478+
skip = false
479+
for attr in f.attrs {
480+
if attr == 'skip' || (attr.starts_with('structlog: ')
481+
&& attr.all_after('structlog: ').trim('"\'') == '-') {
482+
skip = true
483+
}
484+
if attr == 'omitempty' {
485+
omitempty = true
486+
}
487+
}
488+
value := s.$(f.name)
489+
if omitempty {
490+
skip = check_is_empty(value) or { false }
491+
}
492+
if !skip {
493+
fields << field(f.name, value)
494+
}
495+
}
496+
return fields
497+
}
498+
499+
fn check_is_empty[T](val T) ?bool {
500+
$if val is string {
501+
if val == '' {
502+
return false
503+
}
504+
} $else $if val is $int || val is $float {
505+
if val == 0 {
506+
return false
507+
}
508+
} $else $if val is ?string {
509+
return val ? != ''
510+
} $else $if val is ?int {
511+
return val ? != 0
512+
} $else $if val is ?f64 || val is ?f32 {
513+
return val ? != 0.0
514+
}
515+
return true
516+
}
517+
518+
// field creates new `Field` with given name and value.
519+
// Map values will be transformed to `map[string]Value`.
520+
pub fn field[T](name string, value T) Field {
521+
$if value is $struct {
522+
$compile_error('structlog.field: cannot pass struct as field value')
523+
}
524+
$if value is $map {
525+
mut value_map := map[string]Value{}
526+
for k, v in value {
527+
value_map[k.str()] = Value(v)
528+
}
529+
return Field{name, value_map}
530+
} $else {
531+
return Field{name, value}
532+
}
533+
}

v.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Module {
22
name: 'structlog'
33
description: 'Structured logs'
4-
version: '0.3.0'
4+
version: '0.4.0'
55
license: 'MIT'
66
dependencies: []
77
}

0 commit comments

Comments
 (0)