/
logger.go
245 lines (217 loc) · 6.57 KB
/
logger.go
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// Package Oris_Log allows users to write application logs to file, console or DB (NoSql/Sql)
//
// The logger allows users to add context (context are more information that can be shared across the logger instance) to logs if needed.
//
// Features
//
//1. Write log to SQL/NoSQL DB.
//
//2. Write log to file.
//
//3. Write log to console.
//
//4. Logs are written in json/plain text format.
//
//5. Logs can be written to multiple output source i.e. to both file and console.
//
//6. Context can be added to a log i.e. User ID, can be added to a log to track user footprint.
//
// Oris logger requires a configuration file `logger.json`:
// {
// "name": "awesomeProject",
// "filename": "sample",
// "MaxFileSize": "2K",
// "folder": "logs",
// "output": "console",
// "buffer": 10000,
// "disable_logType": ["fatal","debug"]
// }
// 1. name: The project name
//
// 2. filename: The name that would be assigned to log file. This config is only needed when using file as output.
//
// 3. MaxFileSize: Set the max file size for each log, ones the file exceeds the max size, it is renamed and subsequent logs are written to a new file.
// Note: A new log file is created every day. The size is a string with suffix 'K' kilobyte and 'M' Megabyte.
//
// 4. folder: the name of the folder where logs file would be kept, the default location is ./logs.
//
// 5. output: This determines where logs would be written to i.e file, console, or MongoDB
//
// 6. buffer: This value is used when output is set to `file`, it buffers file during write to memory to avoid blocking.
//
// 7. disable_logType: This can be used to disable so log type in production i.e. debug log. The allowable values are 'info','debug','error','fatal'
//
package Oris_Log
import (
"fmt"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/mongo"
"path/filepath"
"runtime"
"strings"
"time"
)
// Logger An interface that states the requirements for a writer to be an instance of Logger.
// Every log writer must implement the Logger interface.
type Logger interface {
// Info Logs are written to an output with an info label
Info(interface{}, ...interface{})
// Error Logs are written to an output with an error label
Error(interface{}, ...interface{})
// Debug Logs are written to an output with a debug label
Debug(interface{}, ...interface{})
// Fatal : Logs is been written to output and program terminates immediately
Fatal(interface{}, ...interface{})
// NewContext This function is used to add context to a log record, a new instance of the log writer is returned.
NewContext() Logger
// AddContext Add a new context value to a log writer
AddContext(key string, value interface{})
// GetContext returns context value based on its key
GetContext(key string) interface{}
// SetLogID set ID for the current log context
SetLogID(id string)
}
// Label datatype for log type
type Label string
const (
CONSOLE = "console"
FILE = "file"
SQL = "sql"
MONGODB = "mongo"
LOGDATEFORMAT = "2006-01-02 15:04:05"
DATEFORMAT = "2006_01_02"
DBNAME = "Log"
MAXFILESIZE int64 = 214748364800
INFO Label = "INFO"
ERROR Label = "ERROR"
DEBUG Label = "DEBUG"
FATAL Label = "FATAL"
)
type config struct {
Name string
Filename string
MaxFileSize string
MaxSize int64
HasMaxSize bool
Folder string
Path string
Output string
BaseDir string
Buf int // Buf only works with file output type
Collection string
Disabled map[string]bool
DisableLogType []string // values 'info','debug','error','fatal'
}
type Configuration struct {
Name string
Filename string
MaxFileSize string
Folder string
Output string
Buffer int
DisableLogType []string
MongoDB *mongo.Database
ColName string
}
// LogFormat outlines the way messages will be formatted in json
type logFormat struct {
Created string `json:"created"`
ID string `json:"id"`
Label Label `json:"label"`
Prefix string `json:"prefix"`
Source string `json:"source"`
Message string `json:"message"`
Context map[string]interface{} `json:"context,omitempty"`
}
// initConfig Initializes log configurations that can be gotten from the `logger.json` file.
func (c *config) initConfig(conf *Configuration) {
_, b, _, _ := runtime.Caller(2)
c.BaseDir = filepath.Dir(b)
c.Name = conf.Name
c.Filename = conf.Filename
c.MaxFileSize = conf.MaxFileSize
c.Folder = conf.Folder
c.Output = conf.Output
c.Buf = conf.Buffer
c.DisableLogType = conf.DisableLogType
c.Collection = conf.ColName
if c.Filename == "" {
c.Filename = "log.json"
} else if !strings.HasSuffix(strings.TrimSpace(c.Filename), ".json") {
c.Filename = fmt.Sprintf("%s.json", c.Filename)
}
if c.Output == "" {
c.Output = CONSOLE
}
if c.Collection == "" {
c.Collection = DBNAME
}
if strings.TrimSpace(c.Folder) == "" {
c.Folder = "logs"
}
if strings.TrimSpace(c.MaxFileSize) != "" {
c.HasMaxSize = true
c.MaxSize = ConvertSize(c.MaxFileSize)
}
c.BaseDir = fmt.Sprintf("%s/%s", c.BaseDir, c.Folder)
c.Path = fmt.Sprintf("%s/%s", c.BaseDir, c.Filename)
if c.Buf == 0 || c.Buf < 5000 {
c.Buf = 10000
}
c.Disabled = make(map[string]bool)
if len(c.DisableLogType) > 0 {
for _, v := range c.DisableLogType[:4] {
c.Disabled[v] = true
}
}
}
// New create a logger instance
func New(conf Configuration) Logger {
// Initialize configuration file
configs := &config{}
configs.initConfig(&conf)
switch configs.Output {
case CONSOLE:
return &ConsoleWriter{config: configs, ID: uuid.New().String()}
case FILE:
{
//panic("File writer not implemented")
file := &FileWriter{config: configs, ch: make(chan logFormat, configs.Buf), ID: uuid.New().String()}
go processor(file.ch, file)
return file
}
case SQL:
{
panic("SQL writer not implemented")
}
case MONGODB:
{
panic("Mongo writer not implemented")
// Instance of mongo DB Collection
col := conf.MongoDB.Collection(configs.Collection)
return &MongoWriter{
config: configs,
db: col,
ID: uuid.New().String(),
}
}
}
return &ConsoleWriter{config: configs, ID: uuid.New().String()}
}
// getSource returns the name of the function caller and the line where the call was made
func getSource(depth int) string {
_, file, line, _ := runtime.Caller(depth) // 2
caller := filepath.Base(file)
return fmt.Sprintf("%s:%d", caller, line)
}
func prepareLog(message, prefix string, ctx map[string]interface{}, ltype Label, ID string) logFormat {
return logFormat{
ID: ID,
Created: time.Now().Format(LOGDATEFORMAT),
Label: ltype,
Prefix: prefix,
Source: getSource(3),
Context: ctx,
Message: message,
}
}