mirror of
https://github.com/grafana/grafana.git
synced 2025-08-02 00:01:48 +08:00

* feat: pass read-only props to extension components This initial commit is used to verify that things are broken in case the datasource object is not cloned before passed in as a prop. * chore: update tests --------- Co-authored-by: Hugo Häggmark <hugo.haggmark@gmail.com>
105 lines
3.3 KiB
TypeScript
105 lines
3.3 KiB
TypeScript
import { isString } from 'lodash';
|
|
import { nanoid } from 'nanoid';
|
|
import { Observable, ReplaySubject } from 'rxjs';
|
|
|
|
import { Labels, LogLevel } from '@grafana/data';
|
|
import { config, createMonitoringLogger } from '@grafana/runtime';
|
|
|
|
export type ExtensionsLogItem = {
|
|
level: LogLevel;
|
|
timestamp: number;
|
|
labels: Labels;
|
|
message: string;
|
|
id: string;
|
|
pluginId?: string;
|
|
extensionPointId?: string;
|
|
};
|
|
|
|
const channelName = 'ui-extension-logs';
|
|
const logsNumberLimit = 1000;
|
|
const logsRetentionTime = 1000 * 60 * 10;
|
|
const monitoringLogger = createMonitoringLogger(channelName);
|
|
|
|
export class ExtensionsLog {
|
|
private baseLabels: Labels | undefined;
|
|
private subject: ReplaySubject<ExtensionsLogItem>;
|
|
private channel: BroadcastChannel;
|
|
|
|
constructor(baseLabels?: Labels, subject?: ReplaySubject<ExtensionsLogItem>, channel?: BroadcastChannel) {
|
|
this.baseLabels = baseLabels;
|
|
this.channel = channel ?? new BroadcastChannel(channelName);
|
|
this.subject = subject ?? new ReplaySubject<ExtensionsLogItem>(logsNumberLimit, logsRetentionTime);
|
|
|
|
if (!channel) {
|
|
this.channel.onmessage = (msg: MessageEvent<ExtensionsLogItem>) => this.subject.next(msg.data);
|
|
}
|
|
}
|
|
|
|
info(message: string, labels?: Labels): void {
|
|
this.log(LogLevel.info, message, labels);
|
|
}
|
|
|
|
warning(message: string, labels?: Labels): void {
|
|
monitoringLogger.logWarning(message, { ...this.baseLabels, ...labels });
|
|
config.buildInfo.env === 'development' && console.warn(message, { ...this.baseLabels, ...labels });
|
|
this.log(LogLevel.warning, message, labels);
|
|
}
|
|
|
|
error(message: string, labels?: Labels): void {
|
|
// TODO: If Faro has console instrumentation, then the following will track the same error message twice
|
|
// (first: `monitoringLogger.logError()`, second: `console.error()` which gets picked up by Faro)
|
|
monitoringLogger.logError(new Error(message), { ...this.baseLabels, ...labels });
|
|
console.error(message, { ...this.baseLabels, ...labels });
|
|
this.log(LogLevel.error, message, labels);
|
|
}
|
|
|
|
debug(message: string, labels?: Labels): void {
|
|
this.log(LogLevel.debug, message, labels);
|
|
}
|
|
|
|
trace(message: string, labels?: Labels): void {
|
|
this.log(LogLevel.trace, message, labels);
|
|
}
|
|
|
|
fatal(message: string, labels?: Labels): void {
|
|
this.log(LogLevel.fatal, message, labels);
|
|
}
|
|
|
|
private log(level: LogLevel, message: string, labels?: Labels): void {
|
|
const combinedLabels = { ...labels, ...this.baseLabels };
|
|
const { pluginId, extensionPointId } = combinedLabels;
|
|
|
|
const item: ExtensionsLogItem = {
|
|
level: level,
|
|
labels: combinedLabels,
|
|
timestamp: Date.now(),
|
|
id: nanoid(),
|
|
message: message,
|
|
pluginId: isString(pluginId) ? pluginId : undefined,
|
|
extensionPointId: isString(extensionPointId) ? extensionPointId : undefined,
|
|
};
|
|
|
|
// We only receive messages from different contexts so adding the ones
|
|
// pushed by this log to the local subject.
|
|
this.subject.next(item);
|
|
this.channel.postMessage(item);
|
|
}
|
|
|
|
asObservable(): Observable<ExtensionsLogItem> {
|
|
return this.subject.asObservable();
|
|
}
|
|
|
|
child(labels: Labels): ExtensionsLog {
|
|
return new ExtensionsLog(
|
|
{
|
|
...labels,
|
|
...this.baseLabels,
|
|
},
|
|
this.subject,
|
|
this.channel
|
|
);
|
|
}
|
|
}
|
|
|
|
export const log = new ExtensionsLog();
|