-
-
Notifications
You must be signed in to change notification settings - Fork 5
/
ServiceBaseClass.impl.ts
202 lines (176 loc) · 5.74 KB
/
ServiceBaseClass.impl.ts
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
import type { Context, Span, SpanOptions } from '@opentelemetry/api'
import { SpanStatusCode } from '@opentelemetry/api'
import { Resource } from '@opentelemetry/resources'
import type { SpanProcessor } from '@opentelemetry/sdk-trace-node'
import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'
import type { Schema } from '@typeschema/main'
import { puristaVersion } from '../../../version.js'
import type { ConfigStore } from '../../ConfigStore/index.js'
import type { EventBridge } from '../../EventBridge/index.js'
import type { SecretStore } from '../../SecretStore/index.js'
import type { StateStore } from '../../StateStore/index.js'
import type { Logger, ServiceEvents, ServiceInfoType } from '../../types/index.js'
import { GenericEventEmitter, PuristaSpanTag } from '../../types/index.js'
import { ServiceInfoValidator } from '../ServiceInfoValidator.impl.js'
/**
* Class which contains basic functions that are not directly related to
*
* - handling of messages
* - handling of commands
* - handling of subscriptions
*
* @group Service
*/
export class ServiceBaseClass extends GenericEventEmitter<ServiceEvents> {
readonly info: ServiceInfoType
protected eventBridge: EventBridge
protected logger: Logger
spanProcessor: SpanProcessor | undefined
traceProvider: NodeTracerProvider
protected secretStore: SecretStore
protected configStore: ConfigStore
protected stateStore: StateStore
protected configSchema: Schema | undefined
constructor(options: {
logger: Logger
info: ServiceInfoType
eventBridge: EventBridge
spanProcessor?: SpanProcessor
secretStore: SecretStore
configStore: ConfigStore
stateStore: StateStore
configSchema?: Schema
}) {
super()
this.info = new Proxy(
{
serviceName: '',
serviceDescription: '',
serviceVersion: '1',
},
ServiceInfoValidator,
)
this.info.serviceDescription = options.info.serviceDescription
this.info.serviceName = options.info.serviceName
this.info.serviceVersion = options.info.serviceVersion
this.logger = options.logger.getChildLogger({
serviceName: this.info.serviceName,
serviceVersion: this.info.serviceVersion,
puristaVersion,
})
this.logger.debug({ ...this.info }, `creating ${this.info.serviceName} ${this.info.serviceVersion}`)
const resource = Resource.default().merge(
new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: this.info.serviceName,
[SemanticResourceAttributes.SERVICE_VERSION]: this.info.serviceVersion,
}),
)
this.traceProvider = new NodeTracerProvider({
resource,
})
if (options.spanProcessor) {
this.traceProvider.addSpanProcessor(options.spanProcessor)
}
this.traceProvider.register()
this.spanProcessor = options.spanProcessor
this.eventBridge = options.eventBridge
this.secretStore = options.secretStore
this.configStore = options.configStore
this.stateStore = options.stateStore
}
/**
* Get service info
*/
get serviceInfo(): ServiceInfoType {
return Object.freeze({ ...this.info })
}
/**
* Returns open telemetry tracer of this service
*
* @returns Tracer
*/
getTracer(name?: string, version?: string) {
return this.traceProvider.getTracer(
name ?? this.serviceInfo.serviceName,
version ?? this.serviceInfo.serviceVersion,
)
}
/**
* Start a child span for opentelemetry tracking
* @param name name of span
* @param opts span options
* @param context optional context
* @param fn function to be executed within the span
* @returns return value of fn
*/
async startActiveSpan<F>(
name: string,
opts: SpanOptions,
context: Context | undefined = undefined,
fn: (span: Span) => Promise<F>,
): Promise<F> {
const tracer = this.getTracer()
const callback = async (span: Span) => {
span.setAttribute(PuristaSpanTag.PuristaVersion, puristaVersion)
try {
return await fn(span)
} catch (error) {
let message = 'error'
if (error instanceof Error) {
message = error.message
}
span.recordException(error as Error)
span.setStatus({
code: SpanStatusCode.ERROR,
message,
})
throw error
} finally {
span.end()
}
}
return context
? tracer.startActiveSpan(name, opts, context, callback)
: tracer.startActiveSpan(name, opts, callback)
}
/**
* Start span for opentelemetry tracking on same level.
* The created span will not become the "active" span within opentelemetry!
*
* This means during logging and similar the spanId of parent span is logged.
*
* Use wrapInSpan for marking points in flow of one bigger function,
* but not to trace the program flow itself
*
* @param name name of span
* @param opts span options
* @param fn function te be executed in the span
* @param context span context
* @returns return value of fn
*/
async wrapInSpan<F>(name: string, opts: SpanOptions, fn: (span: Span) => Promise<F>, context?: Context): Promise<F> {
const tracer = this.getTracer()
const span = tracer.startSpan(name, opts, context)
span.setAttribute(PuristaSpanTag.PuristaVersion, puristaVersion)
try {
return await fn(span)
} catch (error) {
let message = 'error'
if (error instanceof Error) {
message = error.message
}
span.recordException(error as Error)
span.setStatus({
code: SpanStatusCode.ERROR,
message,
})
throw error
} finally {
span.end()
}
}
async destroy() {
this.logger.info({ ...this.info }, 'stopped')
}
}