-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogx.mbt
More file actions
230 lines (207 loc) · 6.5 KB
/
Copy pathlogx.mbt
File metadata and controls
230 lines (207 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
// go-zero's `logx`, over `moonlog`'s sink.
//
// What stays here is go-zero's: the four level names its `etc/*.yaml` carries,
// the `@timestamp`/`content` keys its collectors index, the `WithDuration`
// view, and a process-wide logger whose level and destination move at runtime.
// What a record is, and where one goes, is `moonlog`'s — so a service can point
// moonzero, mooncat and anything else in the family at one sink.
///|
/// The bits of a logger that are shared by every view derived from it, so
/// `set_level` on the process logger is seen by a `with_duration` view taken
/// before it (← logx's package-level level and writer).
struct LogState {
mut level : LogLevel
mut sink : &@moonlog.Sink
}
///|
/// A leveled, structured logger (← go-zero's `logx`): entries below the
/// configured level are dropped without being rendered, and everything else
/// reaches the sink as one record carrying `@timestamp`, `level`, `content`, an
/// optional `duration`, and any extra fields.
///
/// The timestamp is milliseconds from the injected `Clock`, not a formatted
/// calendar time: MoonBit has no portable date formatter, and the clock is
/// already how every other timed layer here reads the time.
pub struct Logger {
state : LogState
clock : Clock
duration_ms : Int64?
}
///|
/// The key the entry's time is written under (← go-zero's `logx`, which names
/// it for Elasticsearch's own field).
pub let stamp_key : String = "@timestamp"
///|
/// The key the message is written under (← go-zero's `logx.content`, where zap
/// and `slog` would write `msg`).
pub let message_key : String = "content"
///|
/// The key the elapsed time is written under.
pub let duration_key : String = "duration"
///|
/// One entry as the single-line JSON object go-zero emits: `@timestamp`,
/// `level`, `content`, then whatever else the entry carries.
///
/// This is the default sink's format, not a rule — hand `Logger::new` another
/// `moonlog` sink and the same records are rendered that sink's way.
pub fn entry(record : @moonlog.Record) -> String {
let out : Map[String, Json] = Map([])
for field in record.fields {
if field.0 == stamp_key {
out[stamp_key] = field.1
}
}
out[@moonlog.level_key] = Json::string(level_name(record.level))
out[message_key] = Json::string(record.message)
for field in record.fields {
if field.0 != stamp_key {
out[field.0] = field.1
}
}
Json::object(out).stringify()
}
///|
/// go-zero's name for a level. `Fatal` reads as `severe`, which is what go-zero
/// calls its top level and what its collectors filter on.
pub fn level_name(level : @moonlog.Level) -> String {
match level {
Trace => "trace"
Debug => "debug"
Info => "info"
Warning => "warn"
Error => "error"
Fatal => "severe"
}
}
///|
/// A logger writing to `sink` at `level` and above.
///
/// The default sink prints one [`entry`] per line. `Logger::new(clock,
/// sink=@moonlog.Discard::{ })` is how a test silences one.
pub fn Logger::new(
clock : Clock,
level? : LogLevel = Info,
sink? : &@moonlog.Sink = @moonlog.Lines::new(
line => println(line),
format=entry,
),
) -> Logger {
{ state: { level, sink, }, clock, duration_ms: None, }
}
///|
/// The level below which entries are dropped.
pub fn Logger::level(self : Logger) -> LogLevel {
self.state.level
}
///|
/// Raise or lower the threshold (← `logx.SetLevel`), for this logger and every
/// view derived from it.
pub fn Logger::set_level(self : Logger, level : LogLevel) -> Unit {
self.state.level = level
}
///|
/// Send entries to another sink, for this logger and every view derived from
/// it — a file, a collector client, a buffer under test.
pub fn Logger::set_sink(self : Logger, sink : &@moonlog.Sink) -> Unit {
self.state.sink = sink
}
///|
/// Send entries somewhere other than stdout (← `logx.SetWriter`), keeping
/// go-zero's line format. [`Logger::set_sink`] changes the format too.
pub fn Logger::set_writer(self : Logger, writer : (String) -> Unit) -> Unit {
self.state.sink = @moonlog.Lines::new(writer, format=entry)
}
///|
/// Whether an entry at `level` would be written.
pub fn Logger::enabled(self : Logger, level : LogLevel) -> Bool {
level >= self.state.level
}
///|
/// A view of this logger that stamps `duration` on every entry (←
/// `logx.WithDuration`), sharing the level and sink with the original.
pub fn Logger::with_duration(self : Logger, ms : Int64) -> Logger {
{ state: self.state, clock: self.clock, duration_ms: Some(ms), }
}
///|
/// Render and write one entry, unless `level` is below the threshold.
pub fn Logger::log(
self : Logger,
level : LogLevel,
content : String,
fields? : Array[(String, Json)] = [],
) -> Unit {
if !self.enabled(level) {
return
}
let carried : Array[(String, Json)] = [
(stamp_key, Json::number(self.clock.now().to_double())),
]
match self.duration_ms {
Some(ms) => carried.push((duration_key, Json::number(ms.to_double())))
None => ()
}
for field in fields {
carried.push(field)
}
self.state.sink.write({
level: level.as_level(),
message: content,
fields: carried,
})
}
///|
/// Write a `debug` entry.
pub fn Logger::debug(
self : Logger,
content : String,
fields? : Array[(String, Json)] = [],
) -> Unit {
self.log(Debug, content, fields~)
}
///|
/// Write an `info` entry.
pub fn Logger::info(
self : Logger,
content : String,
fields? : Array[(String, Json)] = [],
) -> Unit {
self.log(Info, content, fields~)
}
///|
/// Write an `error` entry.
pub fn Logger::error(
self : Logger,
content : String,
fields? : Array[(String, Json)] = [],
) -> Unit {
self.log(Error, content, fields~)
}
///|
/// Write a `severe` entry.
pub fn Logger::severe(
self : Logger,
content : String,
fields? : Array[(String, Json)] = [],
) -> Unit {
self.log(Severe, content, fields~)
}
///|
/// The process-wide logger (← logx's package-level logger): what the middlewares
/// write to when no logger is handed to them, on the system clock at `info`.
/// `RestEngine::new` points its level at the loaded `Log.Level`.
pub let logx : Logger = Logger::new(Clock::system())
///|
/// Set the process logger's level (← `logx.SetLevel`).
pub fn set_log_level(level : LogLevel) -> Unit {
logx.set_level(level)
}
///|
/// Send the process logger's entries to `writer` (← `logx.SetWriter`).
pub fn set_log_writer(writer : (String) -> Unit) -> Unit {
logx.set_writer(writer)
}
///|
/// Send the process logger's entries to `sink`.
pub fn set_log_sink(sink : &@moonlog.Sink) -> Unit {
logx.set_sink(sink)
}