Dandi's splits the responsibilities of logging into two primary parts:
-
Logger
is the primary interface that developers will interact with - implementations ofLogger
are used to generate the log entries themselves, and can draw in contextual metadata about where they are called from. An app will have a singleLogger
instance. If no provider forLogger
is available, the app will useNoopLogger
by default. -
LogListener
implementations receive the log data generated by theLogger
. What they do with that data depends on the implementation -ConsoleLogListener
, for example, formats the log data and sends it to the program'sconsole
output. Other implementations could store the data locally, or transmit it to a remote API. An app may use any number ofLogListener
implementations simultaneously.
By default, this pattern is implemented using an RxJS Subject
. The Subject
instance is defined using the LogStream
injection token. The Logger
instance calls next()
on the LogStream
for each log entry. LogListener
instances
subscribe to the LogStream
as an Observable
.
Note: The DI system in @dandi/core
uses Logger
internally, which is why Logger
is part of the root @dandi/core
package and not @dandi/core/logging
.
- Package: @dandi/core/logging
- Module:
LoggingModule
in @dandi/core/logging - Includes a reference the consuming service in the logging data
// main.ts
import { DandiApplication } from '@dandi/core'
import { LoggingModule } from '@dandi/core/logging' // includes ContextualLogger
const container = new DandiApplication({
providers: [
LoggingModule,
/* other providers */
],
})
// my-service.ts
import { Inject, Injectable, Logger } from '@dandi/core'
@Injectable()
export class MyService {
constructor(@Inject(Logger) private logger: Logger) {
this.logger.debug('Constructed!')
}
}
In the above example, when the instances are created, the ContextualLogger
instance uses the InjectionContext
token
provided by the Dandi DI system to receive a reference to the instance of MyService
that caused it to be created. It
then includes that reference when creating log entries like the call to debug()
.
- Package: @dandi/core
- Module: n/a - used as a fallback by
@dandi/core
- As the name suggests,
NoopLogger
does not generate log entries
When no Logger
implementation is configured, NoopLogger
is used by default.
- Package: @dandi/core/logging
- Module: n/a
- Outputs formatted log data to the environment's
console
Include ConsoleLogListener
by calling the use
method on LoggingModule
.
import { DandiApplication } from '@dandi/core'
import { ConsoleLogListener, LoggingModule } from '@dandi/core/logging'
const container = new DandiApplication({
providers: [
LoggingModule.use(ConsoleLogListener),
/* other providers */
],
})
ConsoleLogListener
can be customized to format the log data as desired.
TODO
TODO
TODO