1 | // Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved. |
2 | // Use of this source code is governed by an MIT license |
3 | // that can be found in the LICENSE file. |
4 | module log |
5 | |
6 | import os |
7 | import time |
8 | |
9 | // Log represents a logging object |
10 | pub struct Log { |
11 | mut: |
12 | level Level |
13 | output_label string |
14 | ofile os.File |
15 | output_target LogTarget // output to console (stdout/stderr) or file or both. |
16 | pub mut: |
17 | output_file_name string // log output to this file |
18 | } |
19 | |
20 | // get_level gets the internal logging level. |
21 | pub fn (l &Log) get_level() Level { |
22 | return l.level |
23 | } |
24 | |
25 | // set_level sets the logging level to `level`. Messges for levels above it will skipped. |
26 | // For example, after calling log.set_level(.info), log.debug('message') will produce nothing. |
27 | // Call log.set_level(.disabled) to turn off the logging of all messages. |
28 | pub fn (mut l Log) set_level(level Level) { |
29 | l.level = level |
30 | } |
31 | |
32 | // set_output_level sets the internal logging output to `level`. |
33 | [deprecated: 'use .set_level(level) instead'] |
34 | [deprecated_after: '2023-09-30'] |
35 | pub fn (mut l Log) set_output_level(level Level) { |
36 | l.level = level |
37 | } |
38 | |
39 | // set_full_logpath sets the output label and output path from `full_log_path`. |
40 | pub fn (mut l Log) set_full_logpath(full_log_path string) { |
41 | rlog_file := os.real_path(full_log_path) |
42 | l.set_output_label(os.file_name(rlog_file)) |
43 | l.set_output_path(os.dir(rlog_file)) |
44 | } |
45 | |
46 | // set_output_label sets the `label` for the output. |
47 | pub fn (mut l Log) set_output_label(label string) { |
48 | l.output_label = label |
49 | } |
50 | |
51 | // set_output_path sets the file to which output is logged to. |
52 | pub fn (mut l Log) set_output_path(output_file_path string) { |
53 | if l.ofile.is_opened { |
54 | l.ofile.close() |
55 | } |
56 | l.output_target = .file |
57 | l.output_file_name = os.join_path(os.real_path(output_file_path), l.output_label) |
58 | ofile := os.open_append(l.output_file_name) or { |
59 | panic('error while opening log file ${l.output_file_name} for appending') |
60 | } |
61 | l.ofile = ofile |
62 | } |
63 | |
64 | // log_to_console_too turns on logging to the console too, in addition to logging to a file. |
65 | // You have to call it *after* calling .set_output_path(output_file_path). |
66 | pub fn (mut l Log) log_to_console_too() { |
67 | if l.output_target != .file { |
68 | panic('log_to_console_too should be called *after* .set_output_path') |
69 | } |
70 | l.output_target = .both |
71 | } |
72 | |
73 | // flush writes the log file content to disk. |
74 | pub fn (mut l Log) flush() { |
75 | l.ofile.flush() |
76 | } |
77 | |
78 | // close closes the log file. |
79 | pub fn (mut l Log) close() { |
80 | l.ofile.close() |
81 | } |
82 | |
83 | // log_file writes log line `s` with `level` to the log file. |
84 | fn (mut l Log) log_file(s string, level Level) { |
85 | timestamp := time.now().format_ss_micro() |
86 | e := tag_to_file(level) |
87 | l.ofile.writeln('${timestamp} [${e}] ${s}') or { panic(err) } |
88 | } |
89 | |
90 | // log_cli writes log line `s` with `level` to stdout. |
91 | fn (l &Log) log_cli(s string, level Level) { |
92 | timestamp := time.now().format_ss_micro() |
93 | e := tag_to_cli(level) |
94 | println('${timestamp} [${e}] ${s}') |
95 | } |
96 | |
97 | // send_output writes log line `s` with `level` to either the log file or the console |
98 | // according to the value of the `.output_target` field. |
99 | pub fn (mut l Log) send_output(s &string, level Level) { |
100 | if l.output_target == .file || l.output_target == .both { |
101 | l.log_file(s, level) |
102 | } |
103 | if l.output_target == .console || l.output_target == .both { |
104 | l.log_cli(s, level) |
105 | } |
106 | } |
107 | |
108 | // fatal logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.fatal` category. |
109 | // Note that this method performs a panic at the end, even if log level is not enabled. |
110 | [noreturn] |
111 | pub fn (mut l Log) fatal(s string) { |
112 | if int(l.level) >= int(Level.fatal) { |
113 | l.send_output(s, .fatal) |
114 | l.ofile.close() |
115 | } |
116 | panic('${l.output_label}: ${s}') |
117 | } |
118 | |
119 | // error logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.error` category. |
120 | pub fn (mut l Log) error(s string) { |
121 | if int(l.level) < int(Level.error) { |
122 | return |
123 | } |
124 | l.send_output(s, .error) |
125 | } |
126 | |
127 | // warn logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.warn` category. |
128 | pub fn (mut l Log) warn(s string) { |
129 | if int(l.level) < int(Level.warn) { |
130 | return |
131 | } |
132 | l.send_output(s, .warn) |
133 | } |
134 | |
135 | // info logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.info` category. |
136 | pub fn (mut l Log) info(s string) { |
137 | if int(l.level) < int(Level.info) { |
138 | return |
139 | } |
140 | l.send_output(s, .info) |
141 | } |
142 | |
143 | // debug logs line `s` via `send_output` if `Log.level` is greater than or equal to the `Level.debug` category. |
144 | pub fn (mut l Log) debug(s string) { |
145 | if int(l.level) < int(Level.debug) { |
146 | return |
147 | } |
148 | l.send_output(s, .debug) |
149 | } |
150 | |
151 | // free frees the given Log instance |
152 | [unsafe] |
153 | pub fn (mut f Log) free() { |
154 | unsafe { |
155 | f.output_label.free() |
156 | f.ofile.close() |
157 | f.output_file_name.free() |
158 | } |
159 | } |