Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
class-logger
Advanced tools
Boilerplate-free decorator-based class logging. Log method calls and creation of your class easily with the help of two decorators. No prototype mutation. Highly configurable. Built with TypeScript. Works with Node.js and in browser.
@LogClass()
class Test {
@Log()
method1() {
return 123
}
}
Logs Test.construct. Args: [].
before a class instance is created.
Logs Test.method1. Args: [].
before the method call.
Logs Test.method1 -> done. Args: []. Res: 123.
after it.
Run
npm i class-logger reflect-metadata
If you use TypeScript set in you tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
If you use JavaScript configure your babel to support decorators and class properties
At the top of your project root file add
import 'reflect-metadata'
Your environment must support Proxy. For Node.js it's 6.4.0+, for browsers it's Edge 12+, Firefox 18+, Chrome 49+, Safari 10+.
You can log:
import { LogClass, Log } from 'class-logger'
@LogClass()
class Test {
@Log()
method1() {
return 123
}
@Log()
async methodAsync1() {
// do something asynchronous
return Symbol()
}
@Log()
methodError() {
throw new Error()
}
@Log()
property1 = () => null
@Log()
static methodStatic1(arg1) {
return {
prop1: 'test',
}
}
}
// Logs to the console before the method call:
// 'Test.methodStatic1. Args: [42].'
Test.methodStatic1(42)
// Logs to the console after the method call:
// 'Test.methodStatic1 -> done. Args: [42]. Res: {"prop1":"test"}.'
// Logs to the console before the class' construction:
// 'Test.construct. Args: [].'
const test = new Test()
// Logs to the console before the method call:
// 'Test.method1. Args: [].'
test.method1()
// Logs to the console after the method call:
// 'Test.method1 -> done. Args: []. Res: 123.'
// Logs to the console before the method call:
// 'Test.methodAsync1. Args: [].'
test.methodAsync1()
// Logs to the console after the method call (after the promise is resolved):
// 'Test.methodAsync1 -> done. Args: []. Res: Symbol().'
// Logs to the console before the method call:
// 'Test.methodError. Args: [].'
test.methodError()
// Logs to the console after the method call:
// 'Test.methodError -> error. Args: []. Res: Error {"name":"Error","message":"","stack":"some stack trace"}.'
// Logs to the console before the method call:
// 'Test.property1. Args: [].'
test.property1()
// Logs to the console after the method call:
// 'Test.property1 -> done. Args: []. Res: null.'
Here's how the configuration object looks like:
interface IClassLoggerConfig {
// Function to do the actual logging of the final formatted message.
// It's used to log messages before method calls, after successful method calls and before class construction calls.
// Default: console.log
log?: (message: string) => void
// Function to do the actual logging of the final formatted error message.
// It's used to log messages after error method calls.
// Default: console.error
logError?: (message: string) => void
// An object with methods `start` and `end` used to format message data into a string.
// That string is consumed by `log` and `logError`.
// Scroll down to 'Formatting' section to read more.
// Default: new ClassLoggerFormatterService()
formatter?: {
start: (data: IClassLoggerFormatterStartData) => string
end: (data: IClassLoggerFormatterEndData) => string
}
// Config of what should be included in the final message
include?: {
// Whether to include a list of method arguments.
// Could be a boolean or an object with boolean properties `start` and `end`.
// If it's a boolean, it enables/disables the argument list for all log messages.
// If it's an object, then
// the `start` property enables/disables the argument list for log messages before method calls and class construction calls,
// the `end` property enables/disables the argument list for log messages after successful and error method calls.
// Default: `true`
args:
| boolean
| {
start: boolean
end: boolean
}
// Whether to log class construction or not
// Default: `true`
construct: boolean
// Whether to include the result for log messages after successful method calls
// or the error after error method calls.
// Default: `true`
result: boolean
// Whether to include a formatted instance of the class. Useful if have complex logic inside of your methods relying on some properties in your instance. Read about it more down below in a dedicated section.
// Could be a boolean or an object with boolean properties `start` and `end`.
// If it's a boolean, it enables/disables the formatted class instance for all log messages.
// If it's an object, then
// the `start` property enables/disables the formatted class instance for log messages before method calls and class construction calls,
// the `end` property enables/disables the formatted class instance for log messages after successful and error method calls.
// Default: `false`
classInstance:
| boolean
| {
start: boolean
end: boolean
}
}
}
There're 3 layers of config:
Every time class-logger
logs a message all 3 of them are merged together.
You can set it using setConfig
function from class-logger
.
import { setConfig } from 'class-logger'
setConfig({
// Your config override.
// It's merged with the default config.
})
You can set it using LogClass
decorator from class-logger
.
import { LogClass } from 'class-logger'
LogClass({
// Your config override.
// It's later merged with the global config for every method call.
// It means you can set it dynamically.
})
class Test {}
You can set it using Log
decorator from class-logger
.
import { Log } from 'class-logger'
LogClass()
class Test {
@Log({
// Your config override.
// It's later merged with the class config and the global config for every method call.
// It means you can set it dynamically.
})
method1() {}
}
It enables/disabled including the formatted class instance to your log messages. But what does 'formatted' really mean here? So if you decide to include it (remember, it's false
by default), default class formatter (ClassLoggerFormatterService
) is going to execute this sequence:
function
type.
function
properties are just immutable arrow functions used instead of regular class methods to preserve this
context. It doesn't make much sense to bloat your logs with stringified bodies of those functions.ClassLoggerFormatterService
considers an object a plain object if its prototype is strictly equal to Object.prototype
.Example:
class ServiceA {}
@LogClass({
include: {
classInstance: true,
},
})
class Test {
private serviceA = new ServiceA()
private prop1 = 42
private prop2 = { test: 42 }
private method1 = () => null
@Log()
public method2() {
return 42
}
}
// Logs to the console before the class' construction:
// 'Test.construct. Args: []. Class instance: {"serviceA": ServiceA {},"prop1":42,"prop2":{"test":42}}.'
const test = new Test()
// Logs to the console before the method call:
// 'Test.method2. Args: []. Class instance: {"serviceA": ServiceA {},"prop1":42,"prop2":{"test":42}}.'
test.method2()
// Logs to the console after the method call:
// 'Test.method2 -> done. Args: []. Class instance: {"serviceA": ServiceA {},"prop1":42,"prop2":{"test":42}}. Res: 42.'
If a class instance is not available at the moment (e.g. for class construction or calls of static methods), it logs
N/A
.
{
include: {
args: false
}
}
{
include: {
args: {
start: true
end: false
}
}
}
{
include: {
classInstance: true
}
}
{
include: {
classInstance: {
start: true
end: false
}
}
}
{
include: {
construct: false
}
}
{
include: {
result: false
}
}
{
log: myLogger.debug,
logError: myLogger.error
}
Which could look like this in real world:
import { setConfig } from 'class-logger'
import { createLogger } from 'winston'
const logger = createLogger()
setConfig({
log: logger.debug.bind(logger),
logError: logger.error.bind(logger),
})
You can pass your own custom formatter to the config to format messages to your liking.
{
formatter: myCustomFormatter
}
Your custom formatter must be an object with properties start
and end
. It must comply with the following interface:
interface IClassLoggerFormatter {
start: (data: IClassLoggerFormatterStartData) => string
end: (data: IClassLoggerFormatterEndData) => string
}
where IClassLoggerFormatterStartData
is:
interface IClassLoggerFormatterStartData {
args: any[]
className: string
propertyName: string | symbol
classInstance?: any
include: {
args:
| boolean
| {
start: boolean
end: boolean
}
construct: boolean
result: boolean
classInstance:
| boolean
| {
start: boolean
end: boolean
}
}
}
and IClassLoggerFormatterEndData
is:
interface IClassLoggerFormatterEndData {
args: any[]
className: string
propertyName: string | symbol
classInstance?: any
result: any
error: boolean
include: {
args:
| boolean
| {
start: boolean
end: boolean
}
construct: boolean
result: boolean
classInstance:
| boolean
| {
start: boolean
end: boolean
}
}
}
You can provide your own object with these two properties, but the easiest way to modify the formatting logic of class-logger
is to subclass the default formatter - ClassLoggerFormatterService
.
ClassLoggerFormatterService
has these protected
methods which are building blocks of final messages:
base
operation
args
classInstance
result
final
Generally speaking, start
method of ClassLoggerFormatterService
is base
+ args
+ classInstance
+ final
. end
is base
+ operation
+ args
+ classInstance
+ result
+ final
.
Let's take a look at how we could add a timestamp to the beginning of each message:
import { ClassLoggerFormatterService, IClassLoggerFormatterStartData, setConfig } from 'class-logger'
class ClassLoggerTimestampFormatterService extends ClassLoggerFormatterService {
protected base(data: IClassLoggerFormatterStartData) {
const baseSuper = super.base(data)
const timestamp = Date.now()
const baseWithTimestamp = `${timestamp}:${baseSuper}`
return baseWithTimestamp
}
}
setConfig({
formatter: new ClassLoggerTimestampFormatterService(),
})
FYI, winston, pino and pretty much any other logger are capable of adding timestamps on their own, so this example is purely educative. I'd advice to use your logger's built-in mechanism for creating timestamps if possible.
FAQs
Boilerplate-free decorator-based class logging
The npm package class-logger receives a total of 532 weekly downloads. As such, class-logger popularity was classified as not popular.
We found that class-logger demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.