Skip to content

Commit 9a127b7

Browse files
Merge pull request #78 from shiftcode/#76-logger-n
#76 logger n
2 parents de2c04b + 24e3e7d commit 9a127b7

34 files changed

Lines changed: 1574 additions & 1502 deletions

lerna.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"useNx": false,
33
"packages": ["libs/*", "apps/*"],
4-
"version": "13.0.0",
4+
"version": "14.0.0-pr76.14",
55
"command": {
66
"version": {
77
"allowBranch": "*",

libs/components/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shiftcode/ngx-components",
3-
"version": "13.0.0",
3+
"version": "14.0.0-pr76.14",
44
"repository": "https://github.com/shiftcode/sc-ng-commons-public",
55
"license": "MIT",
66
"author": "shiftcode GmbH <team@shiftcode.ch>",
@@ -14,14 +14,14 @@
1414
"tslib": "^2.5.0"
1515
},
1616
"peerDependencies": {
17-
"@angular/animations": "^21.0.3",
17+
"@angular/animations": "^21.0.0",
1818
"@angular/cdk": "^21.0.0",
19-
"@angular/common": "^21.0.3",
20-
"@angular/core": "^21.0.3",
21-
"@angular/forms": "^21.0.3",
22-
"@angular/router": "^21.0.3",
23-
"@shiftcode/logger": "^3.0.0",
24-
"@shiftcode/ngx-core": "^13.0.0 || ^13.0.0-pr63",
19+
"@angular/common": "^21.0.0",
20+
"@angular/core": "^21.0.0",
21+
"@angular/forms": "^21.0.0",
22+
"@angular/router": "^21.0.0",
23+
"@shiftcode/logger": "^4.0.0 || ^4.0.0-pr250",
24+
"@shiftcode/ngx-core": "^14.0.0 || ^14.0.0-pr76",
2525
"rxjs": "^6.5.3 || ^7.4.0"
2626
}
2727
}

libs/core/package.json

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@shiftcode/ngx-core",
3-
"version": "13.0.0",
3+
"version": "14.0.0-pr76.14",
44
"repository": "https://github.com/shiftcode/sc-ng-commons-public",
55
"license": "MIT",
66
"author": "shiftcode GmbH <team@shiftcode.ch>",
@@ -14,11 +14,11 @@
1414
"tslib": "^2.5.0"
1515
},
1616
"peerDependencies": {
17-
"@angular/common": "^21.0.3",
18-
"@angular/core": "^21.0.3",
19-
"@angular/platform-browser": "^21.0.3",
20-
"@angular/router": "^21.0.3",
21-
"@shiftcode/logger": "^3.0.0",
17+
"@angular/common": "^21.0.0",
18+
"@angular/core": "^21.0.0",
19+
"@angular/platform-browser": "^21.0.0",
20+
"@angular/router": "^21.0.0",
21+
"@shiftcode/logger": "^4.0.0 || ^4.0.0-pr250",
2222
"@shiftcode/utilities": "^4.0.0",
2323
"rxjs": "^6.5.3 || ^7.4.0"
2424
}

libs/core/src/lib/logger/console/console-log-transport.service.ts renamed to libs/core/src/lib/logger/browser-console/browser-console-log-transport.service.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
1-
/* eslint-disable no-console */
21
import { isPlatformServer } from '@angular/common'
3-
import { inject, Injectable, PLATFORM_ID } from '@angular/core'
2+
import { inject, Injectable, InjectionToken, PLATFORM_ID } from '@angular/core'
43
import { LogLevel, LogTransport } from '@shiftcode/logger'
54

6-
import { leadingZero } from '../helper/leading-zero.function'
7-
import { CONSOLE_LOG_TRANSPORT_CONFIG } from './console-log-transport-config.injection-token'
5+
import { loggingTimeFormat } from '../helper/logging-time-format.const'
6+
7+
export interface BrowserConsoleLogTransportConfig {
8+
logLevel: LogLevel
9+
}
10+
11+
export const BROWSER_CONSOLE_LOG_TRANSPORT_CONFIG = new InjectionToken<BrowserConsoleLogTransportConfig>(
12+
'BROWSER_CONSOLE_LOG_TRANSPORT_CONFIG',
13+
{ factory: () => ({ logLevel: LogLevel.DEBUG }) },
14+
)
815

916
@Injectable({ providedIn: 'root' })
10-
export class ConsoleLogTransport extends LogTransport {
17+
export class BrowserConsoleLogTransportService extends LogTransport {
1118
constructor() {
12-
super(inject(CONSOLE_LOG_TRANSPORT_CONFIG).logLevel)
19+
super(inject(BROWSER_CONSOLE_LOG_TRANSPORT_CONFIG).logLevel)
1320
if (isPlatformServer(inject(PLATFORM_ID))) {
1421
throw new Error('This log transport is only for client side use - consider using "NodeConsoleLogTransport"')
1522
}
1623
}
1724

1825
log(level: LogLevel, clazzName: string, color: string, timestamp: Date, args: any[]) {
1926
if (this.isLevelEnabled(level)) {
20-
const now = [
21-
leadingZero(2, timestamp.getHours()),
22-
leadingZero(2, timestamp.getMinutes()),
23-
leadingZero(2, timestamp.getSeconds()),
24-
leadingZero(3, timestamp.getMilliseconds()),
25-
].join(':') // 'HH:mm:ss:SSS'
27+
const now = loggingTimeFormat.format(timestamp)
28+
2629
const firstArgument = args.splice(0, 1)[0]
2730

2831
if (typeof firstArgument === 'string') {
@@ -32,6 +35,7 @@ export class ConsoleLogTransport extends LogTransport {
3235
args.splice(0, 0, `%c${now} - ${clazzName} ::`, `color:${color}`, firstArgument)
3336
}
3437

38+
/* eslint-disable no-console */
3539
switch (level) {
3640
case LogLevel.DEBUG:
3741
console.debug(...args)
@@ -47,7 +51,10 @@ export class ConsoleLogTransport extends LogTransport {
4751
break
4852
case LogLevel.OFF:
4953
break
54+
default:
55+
return level // exhaustive check
5056
}
57+
/* eslint-enable no-console */
5158
}
5259
}
5360
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { LogTransport } from '@shiftcode/logger'
2+
3+
import { ValueOrFactory } from '../helper/value-or-factory.type'
4+
import { LoggerFeature } from '../logger-feature.type'
5+
import { LoggerFeatureKind } from '../logger-feature-kind.enum'
6+
import {
7+
BROWSER_CONSOLE_LOG_TRANSPORT_CONFIG,
8+
BrowserConsoleLogTransportConfig,
9+
BrowserConsoleLogTransportService,
10+
} from './browser-console-log-transport.service'
11+
12+
export function withBrowserConsoleTransport(config: ValueOrFactory<BrowserConsoleLogTransportConfig>): LoggerFeature {
13+
return {
14+
kind: LoggerFeatureKind.TRANSPORT,
15+
providers: [
16+
{
17+
provide: BROWSER_CONSOLE_LOG_TRANSPORT_CONFIG,
18+
...(typeof config === 'function' ? { useFactory: config } : { useValue: config }),
19+
},
20+
{ provide: LogTransport, useClass: BrowserConsoleLogTransportService, multi: true },
21+
],
22+
}
23+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// eslint-disable-next-line max-classes-per-file
2+
import { inject, Injectable } from '@angular/core'
3+
import { ContentType } from '@shiftcode/utilities'
4+
import { CommonHttpHeader } from '@shiftcode/utilities'
5+
6+
import { CLOUD_WATCH_LOG_V2_CONFIG } from './cloud-watch-log-config.injection-token'
7+
8+
export interface LogStream {
9+
logStreamName: string
10+
creationTime: number
11+
lastIngestionTime: number | null
12+
}
13+
14+
export interface LogEvent {
15+
message: string
16+
timestamp: number
17+
}
18+
19+
export interface WriteLogEvents {
20+
logEvents: LogEvent[]
21+
}
22+
23+
export class HttpError extends Error {
24+
override readonly name: string = 'HttpError'
25+
26+
constructor(
27+
readonly statusCode: number,
28+
message: string,
29+
) {
30+
super(`${statusCode} - ${message}`)
31+
}
32+
}
33+
34+
export class HttpApiError extends HttpError {
35+
override readonly name: string = 'HttpApiError'
36+
37+
constructor(
38+
statusCode: number,
39+
readonly errorCode: string,
40+
message: string,
41+
) {
42+
super(statusCode, `${message} (${errorCode})`)
43+
}
44+
}
45+
46+
enum ApiPath {
47+
STREAMS = 'streams',
48+
STREAM_LOGS = 'logs',
49+
}
50+
51+
@Injectable({ providedIn: 'root' })
52+
export class CloudWatchLogV2ApiService {
53+
private readonly apiUrl = inject(CLOUD_WATCH_LOG_V2_CONFIG).apiUrl
54+
55+
async createLogStream(logStreamName: string): Promise<void> {
56+
const resp = await fetch(new URL(ApiPath.STREAMS, this.apiUrl), {
57+
method: 'POST',
58+
headers: { [CommonHttpHeader.CONTENT_TYPE]: ContentType.JSON },
59+
body: JSON.stringify({ logStreamName }),
60+
})
61+
await this.handleError(resp)
62+
}
63+
64+
async describeLogStream(logStreamName: string): Promise<LogStream> {
65+
const result = await fetch(new URL(`${ApiPath.STREAMS}/${logStreamName}`, this.apiUrl), {
66+
method: 'GET',
67+
headers: { [CommonHttpHeader.CONTENT_TYPE]: ContentType.JSON },
68+
})
69+
await this.handleError(result)
70+
return (await result.json()) as LogStream
71+
}
72+
73+
async writeLogs(logStreamName: string, logs: LogEvent[]): Promise<void> {
74+
// we do not use the sendBeacon API since it will fail with cors preflight and would require ugly workarounds
75+
const resp = await fetch(new URL(`${ApiPath.STREAMS}/${logStreamName}/${ApiPath.STREAM_LOGS}`, this.apiUrl), {
76+
method: 'POST',
77+
headers: { [CommonHttpHeader.CONTENT_TYPE]: ContentType.JSON },
78+
body: JSON.stringify({ logEvents: logs } satisfies WriteLogEvents),
79+
keepalive: true, // do not abort request on page unload
80+
})
81+
await this.handleError(resp)
82+
}
83+
84+
private async handleError(resp: Response): Promise<void> {
85+
if (!resp.ok) {
86+
const errorResponse: object = (await resp.json().catch(() => ({}))) ?? {}
87+
if (
88+
'message' in errorResponse &&
89+
typeof errorResponse.message === 'string' &&
90+
'code' in errorResponse &&
91+
typeof errorResponse.code === 'string'
92+
) {
93+
throw new HttpApiError(resp.status, errorResponse.code, errorResponse.message)
94+
} else {
95+
throw new HttpError(resp.status, 'unknown error')
96+
}
97+
}
98+
}
99+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { InjectionToken } from '@angular/core'
2+
import { LogLevel } from '@shiftcode/logger'
3+
4+
export interface CloudWatchLogV2Config {
5+
logLevel: LogLevel
6+
7+
/**
8+
* the url of the CloudWatchApi Construct from @shiftcode/cdk-utils
9+
*/
10+
apiUrl: string
11+
12+
/** milliseconds until logs are flushed to aws */
13+
flushInterval: number
14+
15+
/**
16+
* replacer function for JSON.stringify
17+
* @default {@link jsonMapSetStringifyReplacer}
18+
*/
19+
jsonStringifyReplacer?: (key: string, value: any) => any
20+
21+
/**
22+
* max number of sub-threshold log events to buffer before dropping oldest.
23+
* @default 100
24+
*/
25+
bufferSize?: number
26+
}
27+
28+
export const CLOUD_WATCH_LOG_V2_CONFIG = new InjectionToken<CloudWatchLogV2Config>('CLOUD_WATCH_LOG_V2_CONFIG')

libs/core/src/lib/logger/cloudwatch/cloud-watch-error-handler.service.ts renamed to libs/core/src/lib/logger/cloud-watch-log/cloud-watch-log-error-handler.service.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import { ErrorHandler, inject, Injectable, Injector } from '@angular/core'
22
import { LogLevel } from '@shiftcode/logger'
33

4-
import { CloudWatchService } from './cloud-watch.service'
4+
import { CloudWatchLogV2Service } from './cloud-watch-log.service'
55

66
/**
77
* Angular ErrorHandler to send uncaught Errors to AWS CloudWatch Logs
8-
* requires the {@link CloudWatchService}
8+
* requires the {@link CloudWatchLogV2Service}
99
*/
1010
@Injectable({ providedIn: 'root' })
11-
export class CloudWatchErrorHandler extends ErrorHandler {
11+
export class CloudWatchLogV2ErrorHandler extends ErrorHandler {
1212
private readonly injector = inject(Injector)
1313

1414
override handleError(error: any): void {
1515
// prevent cyclic dependencies (eg. when CLOUD_WATCH_LOG_TRANSPORT_CONFIG needs config from httpClient request)
16-
const cws = this.injector.get(CloudWatchService)
16+
const cws = this.injector.get(CloudWatchLogV2Service)
1717
cws.addMessage(LogLevel.ERROR, 'BrowserJsException', new Date(), [error])
1818

1919
// call super.handleError to print error the angular way to the console
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { inject, Injectable } from '@angular/core'
2+
import { LogLevel, LogTransport } from '@shiftcode/logger'
3+
4+
import { CloudWatchLogV2Service } from './cloud-watch-log.service'
5+
import { CLOUD_WATCH_LOG_V2_CONFIG } from './cloud-watch-log-config.injection-token'
6+
7+
/**
8+
* The LogTransport implementation using {@link CloudWatchLogV2Service}.
9+
* Delegates all logging logic to the CloudWatchLogger.
10+
* Requires the {@link CLOUD_WATCH_LOG_V2_CONFIG} to be provided.
11+
*/
12+
@Injectable({ providedIn: 'root' })
13+
export class CloudWatchLogV2TransportService extends LogTransport {
14+
private readonly cloudWatchLogger = inject(CloudWatchLogV2Service)
15+
16+
constructor() {
17+
super(inject(CLOUD_WATCH_LOG_V2_CONFIG).logLevel)
18+
}
19+
20+
log(level: LogLevel, clazzName: string, _color: string, timestamp: Date, args: unknown[]): void {
21+
/**
22+
* checking the log level is done in the {@link CloudWatchLogV2Service} in order to do the buffering of below-level logs.
23+
*/
24+
this.cloudWatchLogger.addMessage(level, clazzName, timestamp, args)
25+
}
26+
}

0 commit comments

Comments
 (0)