ac-logger 2.2.3 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ncurc.js +8 -0
- package/CHANGELOG.md +40 -0
- package/README.md +8 -1
- package/index.js +116 -34
- package/package.json +6 -6
- package/test/test.js +24 -0
package/.ncurc.js
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,43 @@
|
|
|
1
|
+
<a name="2.3.2"></a>
|
|
2
|
+
|
|
3
|
+
## [2.3.2](https://github.com/admiralcloud/ac-logger/compare/v2.3.1..v2.3.2) (2024-05-08 15:32:14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fix
|
|
7
|
+
|
|
8
|
+
* **App:** Added option to change logLevel during runtime | MP | [1e59bed4b7d56dff95584fdf528f4dd931e3e6a2](https://github.com/admiralcloud/ac-logger/commit/1e59bed4b7d56dff95584fdf528f4dd931e3e6a2)
|
|
9
|
+
It is now possible to change loglevel after init
|
|
10
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
11
|
+
### Chores
|
|
12
|
+
|
|
13
|
+
* **App:** Updated packages | MP | [af832d3e7cb148e625b0bcd0756d69044bcb812d](https://github.com/admiralcloud/ac-logger/commit/af832d3e7cb148e625b0bcd0756d69044bcb812d)
|
|
14
|
+
Updated packages
|
|
15
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
16
|
+
<a name="2.3.1"></a>
|
|
17
|
+
|
|
18
|
+
## [2.3.1](https://github.com/admiralcloud/ac-logger/compare/v2.3.0..v2.3.1) (2024-03-10 12:31:44)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fix
|
|
22
|
+
|
|
23
|
+
* **App:** Minor improvement for backwards compatibily | MP | [462a6c38269dafccde1f18ffd8a079ac19022269](https://github.com/admiralcloud/ac-logger/commit/462a6c38269dafccde1f18ffd8a079ac19022269)
|
|
24
|
+
Minor improvement for backwards compatibility
|
|
25
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
26
|
+
<a name="2.3.0"></a>
|
|
27
|
+
|
|
28
|
+
# [2.3.0](https://github.com/admiralcloud/ac-logger/compare/v2.2.3..v2.3.0) (2024-03-10 11:09:52)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Feature
|
|
32
|
+
|
|
33
|
+
* **App:** Improved application logs | MP | [5d505c148e44045daf40909092542d7e96949f74](https://github.com/admiralcloud/ac-logger/commit/5d505c148e44045daf40909092542d7e96949f74)
|
|
34
|
+
ac-logger now supports machine readable application logs (if enabled). This way analyzing logs in Cloudwatch (or other apps) will be much easier.
|
|
35
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
36
|
+
### Chores
|
|
37
|
+
|
|
38
|
+
* **App:** Updated packages | MP | [d2fe6c8ade4f1058e0315c30a8f4c85686f03181](https://github.com/admiralcloud/ac-logger/commit/d2fe6c8ade4f1058e0315c30a8f4c85686f03181)
|
|
39
|
+
Updated packages
|
|
40
|
+
Related issues: [undefined/undefined#master](undefined/browse/master)
|
|
1
41
|
<a name="2.2.3"></a>
|
|
2
42
|
|
|
3
43
|
## [2.2.3](https://github.com/admiralcloud/ac-logger/compare/v2.2.2..v2.2.3) (2024-01-01 13:10:51)
|
package/README.md
CHANGED
|
@@ -14,12 +14,16 @@ const aclog = require('ac-logger')
|
|
|
14
14
|
const logConfig = {
|
|
15
15
|
prefixFields: [
|
|
16
16
|
{ field: 'jobId', short: 'J' },
|
|
17
|
-
]
|
|
17
|
+
],
|
|
18
|
+
applicationLogs: {
|
|
19
|
+
enabled: true // if true, JSON formatted, machine readable application logs will be created into logs directory
|
|
20
|
+
}
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
app = {} //
|
|
21
24
|
app.log = aclog({ prefixFields: logConfig.prefixFields }).acLogger
|
|
22
25
|
|
|
26
|
+
// DEPRECATED
|
|
23
27
|
let logMeta = {
|
|
24
28
|
fileName: 'UploadS3',
|
|
25
29
|
functionName: 'upload',
|
|
@@ -28,6 +32,9 @@ let logMeta = {
|
|
|
28
32
|
app.log.info('Message for %s', 'some string', { meta: logMeta })
|
|
29
33
|
// -> INFO UploadS3 | upload | J 193 | Message for some string
|
|
30
34
|
|
|
35
|
+
// MODERN APPROACH (that is best suited for machine and human readability)
|
|
36
|
+
app.log.info('Message for some string', { functionName: 'upload', ... }) // all meta data like "functionName" will be available in application logs
|
|
37
|
+
|
|
31
38
|
```
|
|
32
39
|
|
|
33
40
|
## Error logging
|
package/index.js
CHANGED
|
@@ -1,43 +1,128 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
2
|
const moment = require('moment')
|
|
3
3
|
const path = require('path')
|
|
4
|
+
const util = require('util')
|
|
4
5
|
|
|
5
6
|
const { createLogger, format, transports, addColors } = require('winston')
|
|
6
7
|
require('winston-daily-rotate-file')
|
|
7
8
|
|
|
8
|
-
module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss', level = (process.env.NODE_ENV === 'production' ? 'info' : 'verbose'), headLength = 80, padLength = 12, customLevels, transporters = [] } = {}) => {
|
|
9
|
+
module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss', level = (process.env.NODE_ENV === 'production' ? 'info' : 'verbose'), headLength = 80, padLength = 12, customLevels, transporters = [], applicationLogs = { enabled: false } } = {}) => {
|
|
9
10
|
const precisionMap = [
|
|
10
11
|
{ precision: 1e6, name: 'ms' },
|
|
11
12
|
{ precision: 1e3, name: 'µs' },
|
|
12
13
|
{ precision: 1, name: 'ns' },
|
|
13
14
|
]
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let prefix = []
|
|
21
|
-
let data = []
|
|
22
|
-
_.forEach(prefixFields, item => {
|
|
23
|
-
if (_.get(meta, _.get(item, 'field'))) {
|
|
24
|
-
prefix.push(_.get(item, 'short'))
|
|
25
|
-
data.push(_.get(meta, _.get(item, 'field')))
|
|
26
|
-
}
|
|
27
|
-
})
|
|
28
|
-
const prefixData = _.size(prefix) ? (_.join(prefix, '/') + ' ' + _.join(data, '/') + ' | ') : ''
|
|
29
|
-
if (e instanceof Error) {
|
|
30
|
-
// log the stack
|
|
31
|
-
console.error(e)
|
|
32
|
-
if (_.get(e, 'message')) message += ' | ' + _.get(e, 'message', '')
|
|
16
|
+
const splatFormatter = format((data) => {
|
|
17
|
+
// Check if splat is available and the message contains placeholder(s)
|
|
18
|
+
if (data[Symbol.for('splat')] && /%[sjdifoO%]/.test(data.message)) {
|
|
19
|
+
data.message = util.format(data.message, ...data[Symbol.for('splat')])
|
|
33
20
|
}
|
|
34
|
-
return
|
|
21
|
+
return data
|
|
35
22
|
})
|
|
36
23
|
|
|
24
|
+
const myFormat = format.printf((data) => {
|
|
25
|
+
const level = data[Symbol.for('level')]
|
|
26
|
+
// http log format
|
|
27
|
+
if (level === 'http' && data?.controller) {
|
|
28
|
+
// display accessKey, link (if applicable)
|
|
29
|
+
let message = data?.message ? ` ${data.message}` : ''
|
|
30
|
+
let cuid = data?.customerId ? data?.customerId : ''
|
|
31
|
+
if (data?.userId) cuid += `/${data.userId} `
|
|
32
|
+
return `${data?.timestamp} ${data?.level} ${data?.ip} ${data?.iso2 || ''} ${data?.accessKey || ''} ${cuid}${data?.controller} ${data?.action}${message} | ${data?.statusCode} | ${data?.performance?.executionTime}ms`
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const meta = data?.meta
|
|
36
|
+
//
|
|
37
|
+
let fileName = _.get(meta, 'fileName') ? _.get(meta, 'fileName') + ' | ' : ''
|
|
38
|
+
let functionName = _.get(meta, 'functionName') ? _.get(meta, 'functionName') + ' | ' : _.get(data, 'functionName') ? _.get(data, 'functionName') + ' | ' : ''
|
|
39
|
+
let subName = _.get(meta, 'sub') ? _.get(meta, 'sub') + ' | ' : _.get(data, 'sub') ? _.get(data, 'sub') + ' | ' : ''
|
|
40
|
+
|
|
41
|
+
// modern approach (fileName, functionName, sub) // TBD with team
|
|
42
|
+
let functionIdentifier = _.get(data, 'functionIdentifier') ? _.get(data, 'functionIdentifier') + ' | ' : ''
|
|
43
|
+
if (!fileName && _.get(data, 'fileName')) fileName = _.get(data, 'fileName')
|
|
44
|
+
if (!functionName && _.get(data, 'functionName')) functionName = _.get(data, 'functionName')
|
|
45
|
+
if (!subName && _.get(data, 'sub')) subName = _.get(data, 'sub')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
let message = data?.message
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
let prefix = []
|
|
52
|
+
let dataFromPrefix = []
|
|
53
|
+
_.forEach(prefixFields, item => {
|
|
54
|
+
if (_.get(meta, _.get(item, 'field'))) {
|
|
55
|
+
prefix.push(_.get(item, 'short'))
|
|
56
|
+
dataFromPrefix.push(_.get(meta, _.get(item, 'field')))
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
const prefixData = _.size(prefix) ? (_.join(prefix, '/') + ' ' + _.join(dataFromPrefix, '/') + ' | ') : ''
|
|
60
|
+
if (data?.e instanceof Error) {
|
|
61
|
+
// log the stack
|
|
62
|
+
console.error(data?.e)
|
|
63
|
+
if (data?.e?.message) message += ' | ' + ( data?.e?.message || '')
|
|
64
|
+
}
|
|
65
|
+
// TODO: for better readability, we shold use padding for fileName, functionname, etc
|
|
66
|
+
return `${data?.timestamp} ${data?.level} ${fileName}${functionName}${functionIdentifier}${subName}${prefixData}${message}`
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
|
|
37
71
|
const logTransports = []
|
|
38
72
|
if (!_.size(transporters)) {
|
|
39
73
|
// default behaviour
|
|
40
|
-
|
|
74
|
+
// human-readable console output
|
|
75
|
+
logTransports.push(new transports.Console({
|
|
76
|
+
format: format.combine(
|
|
77
|
+
format.timestamp({
|
|
78
|
+
format: timestampFormat
|
|
79
|
+
}),
|
|
80
|
+
format.errors({ stack: true }),
|
|
81
|
+
format(info => {
|
|
82
|
+
info.level = _.padEnd(info.level.toUpperCase(), 8)
|
|
83
|
+
return info
|
|
84
|
+
})(),
|
|
85
|
+
format.colorize(),
|
|
86
|
+
splatFormatter(),
|
|
87
|
+
myFormat
|
|
88
|
+
),
|
|
89
|
+
}))
|
|
90
|
+
|
|
91
|
+
// structured logging for analysis and querying within the CloudWatch service.
|
|
92
|
+
if (applicationLogs?.enabled) {
|
|
93
|
+
let filename = applicationLogs?.filename || 'application.log'
|
|
94
|
+
const symlinkName = filename
|
|
95
|
+
if (filename.endsWith('.log')) {
|
|
96
|
+
// If the filename ends with .log, insert -%DATE% before the extension
|
|
97
|
+
filename = filename.replace(/\.log$/, '-%DATE%.log');
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// If the filename doesn't end with .log, append -%DATE%.log
|
|
101
|
+
filename = `${filename}-%DATE%.log`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const transport = new transports.DailyRotateFile({
|
|
105
|
+
filename,
|
|
106
|
+
datePattern: 'YYYY-MM-DD',
|
|
107
|
+
zippedArchive: true, // Enable gzip compression for rotated files
|
|
108
|
+
maxSize: applicationLogs?.maxSize || '100m', // Rotate the file when it reaches 20MB
|
|
109
|
+
maxFiles: applicationLogs?.maxFiles || '7d', // Keep rotated files for 7 days
|
|
110
|
+
dirname: applicationLogs?.firname || './logs', // Specify the directory for log files
|
|
111
|
+
format: format.combine(
|
|
112
|
+
format.timestamp(),
|
|
113
|
+
splatFormatter(),
|
|
114
|
+
format.json()
|
|
115
|
+
),
|
|
116
|
+
createSymlink: true, // Enable symlink creation
|
|
117
|
+
symlinkName: symlinkName // Fixed name for the current log file symlink
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
transport.on('error', error => {
|
|
121
|
+
acLogger.error('ac-logger | ApplicationLogs | TransportError | %j', error?.message)
|
|
122
|
+
})
|
|
123
|
+
logTransports.push(transport)
|
|
124
|
+
}
|
|
125
|
+
|
|
41
126
|
if (process.env.NODE_ENV === 'test') {
|
|
42
127
|
logTransports.push(new transports.File({
|
|
43
128
|
filename: 'test.log',
|
|
@@ -55,19 +140,6 @@ module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss',
|
|
|
55
140
|
|
|
56
141
|
const logConfig = {
|
|
57
142
|
level,
|
|
58
|
-
format: format.combine(
|
|
59
|
-
format.timestamp({
|
|
60
|
-
format: timestampFormat
|
|
61
|
-
}),
|
|
62
|
-
format.errors({ stack: true }),
|
|
63
|
-
format(info => {
|
|
64
|
-
info.level = _.padEnd(info.level.toUpperCase(), 8)
|
|
65
|
-
return info
|
|
66
|
-
})(),
|
|
67
|
-
format.colorize(),
|
|
68
|
-
format.splat(),
|
|
69
|
-
myFormat
|
|
70
|
-
),
|
|
71
143
|
transports: logTransports
|
|
72
144
|
}
|
|
73
145
|
if (_.get(customLevels, 'levels')) _.set(logConfig, 'levels', _.get(customLevels, 'levels'))
|
|
@@ -75,6 +147,15 @@ module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss',
|
|
|
75
147
|
const acLogger = createLogger(logConfig)
|
|
76
148
|
if (_.get(customLevels, 'colors')) addColors(_.get(customLevels, 'colors'))
|
|
77
149
|
|
|
150
|
+
const changeLogLevel = (newLevel) => {
|
|
151
|
+
acLogger.transports.forEach((transport) => {
|
|
152
|
+
const currentLevel = transport.level || acLogger.level; // Fallback to the logger's default level
|
|
153
|
+
if (currentLevel !== newLevel) {
|
|
154
|
+
console.log(`Changing level of ${transport.name} from ${currentLevel} to ${newLevel}`);
|
|
155
|
+
transport.level = newLevel; // Confirm this line is executing
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
};
|
|
78
159
|
|
|
79
160
|
const headline = (params) => {
|
|
80
161
|
acLogger.info('')
|
|
@@ -158,6 +239,7 @@ module.exports = ({ prefixFields = [], timestampFormat = 'YYYY-MM-DD HH:mm:ss',
|
|
|
158
239
|
|
|
159
240
|
return {
|
|
160
241
|
acLogger,
|
|
242
|
+
changeLogLevel,
|
|
161
243
|
headline,
|
|
162
244
|
functionStartLine,
|
|
163
245
|
listing,
|
package/package.json
CHANGED
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
"author": "Mark Poepping (https://www.admiralcloud.com)",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": "admiralcloud/ac-logger",
|
|
6
|
-
"version": "2.2
|
|
6
|
+
"version": "2.3.2",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"lodash": "^4.17.21",
|
|
9
9
|
"moment": "^2.30.1",
|
|
10
|
-
"winston": "^3.
|
|
11
|
-
"winston-daily-rotate-file": "^
|
|
10
|
+
"winston": "^3.13.0",
|
|
11
|
+
"winston-daily-rotate-file": "^5.0.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
14
|
"ac-semantic-release": "^0.4.2",
|
|
15
|
-
"chai": "^4.
|
|
16
|
-
"eslint": "^8.
|
|
15
|
+
"chai": "^4.4.1",
|
|
16
|
+
"eslint": "^8.57.0",
|
|
17
17
|
"intercept-stdout": "^0.1.2",
|
|
18
|
-
"mocha": "^10.
|
|
18
|
+
"mocha": "^10.4.0",
|
|
19
19
|
"mocha-jenkins-reporter": "^0.4.8"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
package/test/test.js
CHANGED
|
@@ -23,6 +23,30 @@ describe('Tests', () => {
|
|
|
23
23
|
expect(lines[9]).to.contain('AppName')
|
|
24
24
|
expect(lines[9]).to.contain('myApp')
|
|
25
25
|
})
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
it('Log info', () => {
|
|
29
|
+
captured_text = ''
|
|
30
|
+
const log = aclog().acLogger
|
|
31
|
+
log.info('Hello Info')
|
|
32
|
+
log.debug('Hello Debug')
|
|
33
|
+
const lines = captured_text.split('\n')
|
|
34
|
+
expect(lines[0]).to.contain('INFO')
|
|
35
|
+
expect(lines[1]).not.to.contain('DEBUG')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('Log debug', () => {
|
|
39
|
+
const loggerSetup = aclog();
|
|
40
|
+
const log = loggerSetup.acLogger
|
|
41
|
+
loggerSetup.changeLogLevel('debug')
|
|
42
|
+
captured_text = ''
|
|
43
|
+
log.info('Hello Info')
|
|
44
|
+
log.debug('Hello Debug')
|
|
45
|
+
const lines = captured_text.split('\n')
|
|
46
|
+
expect(lines[0]).to.contain('INFO')
|
|
47
|
+
expect(lines[1]).to.contain('DEBUG')
|
|
48
|
+
})
|
|
49
|
+
|
|
26
50
|
|
|
27
51
|
it('Unhook', () => {
|
|
28
52
|
unhook_intercept()
|