mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
Frontend logging: Remove Sentry javascript agent support (#67493)
* remove Sentry * fix sourcemap resolve
This commit is contained in:
@@ -66,7 +66,6 @@ import { GA4EchoBackend } from './core/services/echo/backends/analytics/GA4Backe
|
||||
import { GAEchoBackend } from './core/services/echo/backends/analytics/GABackend';
|
||||
import { RudderstackBackend } from './core/services/echo/backends/analytics/RudderstackBackend';
|
||||
import { GrafanaJavascriptAgentBackend } from './core/services/echo/backends/grafana-javascript-agent/GrafanaJavascriptAgentBackend';
|
||||
import { SentryEchoBackend } from './core/services/echo/backends/sentry/SentryBackend';
|
||||
import { KeybindingSrv } from './core/services/keybindingSrv';
|
||||
import { initDevFeatures } from './dev';
|
||||
import { getTimeSrv } from './features/dashboard/services/TimeSrv';
|
||||
@@ -259,15 +258,6 @@ function initEchoSrv() {
|
||||
registerEchoBackend(new PerformanceBackend({}));
|
||||
}
|
||||
|
||||
if (config.sentry.enabled) {
|
||||
registerEchoBackend(
|
||||
new SentryEchoBackend({
|
||||
...config.sentry,
|
||||
user: config.bootData.user,
|
||||
buildInfo: config.buildInfo,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (config.grafanaJavascriptAgent.enabled) {
|
||||
registerEchoBackend(
|
||||
new GrafanaJavascriptAgentBackend({
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { captureException } from '@sentry/browser';
|
||||
|
||||
import { faro } from '@grafana/faro-web-sdk';
|
||||
import { getEchoSrv, EchoEventType } from '@grafana/runtime';
|
||||
|
||||
import { PerformanceEvent } from './backends/PerformanceBackend';
|
||||
@@ -14,6 +13,5 @@ export const reportPerformance = (metric: string, value: number) => {
|
||||
});
|
||||
};
|
||||
|
||||
// Sentry will process the error, adding its own metadata, applying any sampling rules,
|
||||
// then push it to EchoSrv as SentryEvent
|
||||
export const reportError = (error: Error) => captureException(error);
|
||||
// Farp will process the error, then push it to EchoSrv as GrafanaJavascriptAgent event
|
||||
export const reportError = (error: Error) => faro?.api?.pushError(error);
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
import { init as initSentry, setUser as sentrySetUser, Event as SentryEvent } from '@sentry/browser';
|
||||
import { FetchTransport } from '@sentry/browser/dist/transports';
|
||||
import { waitFor } from '@testing-library/react';
|
||||
|
||||
import { BuildInfo } from '@grafana/data';
|
||||
import { GrafanaEdition } from '@grafana/data/src/types/config';
|
||||
import { EchoBackend, EchoEventType, EchoMeta, setEchoSrv } from '@grafana/runtime';
|
||||
|
||||
import { Echo } from '../../Echo';
|
||||
|
||||
import { SentryEchoBackend, SentryEchoBackendOptions } from './SentryBackend';
|
||||
import { CustomEndpointTransport } from './transports/CustomEndpointTransport';
|
||||
import { EchoSrvTransport } from './transports/EchoSrvTransport';
|
||||
import { SentryEchoEvent } from './types';
|
||||
|
||||
jest.mock('@sentry/browser');
|
||||
|
||||
describe('SentryEchoBackend', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
window.fetch = jest.fn();
|
||||
});
|
||||
|
||||
const buildInfo: BuildInfo = {
|
||||
version: '1.0',
|
||||
commit: 'abcd123',
|
||||
env: 'production',
|
||||
edition: GrafanaEdition.OpenSource,
|
||||
latestVersion: 'ba',
|
||||
hasUpdate: false,
|
||||
hideVersion: false,
|
||||
};
|
||||
|
||||
const options: SentryEchoBackendOptions = {
|
||||
enabled: true,
|
||||
buildInfo,
|
||||
dsn: 'https://examplePublicKey@o0.ingest.testsentry.io/0',
|
||||
sampleRate: 1,
|
||||
customEndpoint: '',
|
||||
user: {
|
||||
email: 'darth.vader@sith.glx',
|
||||
id: 504,
|
||||
orgId: 1,
|
||||
},
|
||||
};
|
||||
|
||||
it('will set up sentry`s FetchTransport if DSN is provided', async () => {
|
||||
const backend = new SentryEchoBackend(options);
|
||||
expect(backend.transports.length).toEqual(1);
|
||||
expect(backend.transports[0]).toBeInstanceOf(FetchTransport);
|
||||
expect((backend.transports[0] as FetchTransport).options.dsn).toEqual(options.dsn);
|
||||
});
|
||||
|
||||
it('will set up custom endpoint transport if custom endpoint is provided', async () => {
|
||||
const backend = new SentryEchoBackend({
|
||||
...options,
|
||||
dsn: '',
|
||||
customEndpoint: '/log',
|
||||
});
|
||||
expect(backend.transports.length).toEqual(1);
|
||||
expect(backend.transports[0]).toBeInstanceOf(CustomEndpointTransport);
|
||||
expect((backend.transports[0] as CustomEndpointTransport).options.endpoint).toEqual('/log');
|
||||
});
|
||||
|
||||
it('will initialize sentry and set user', async () => {
|
||||
new SentryEchoBackend(options);
|
||||
expect(initSentry).toHaveBeenCalledTimes(1);
|
||||
expect(initSentry).toHaveBeenCalledWith({
|
||||
release: buildInfo.version,
|
||||
environment: buildInfo.env,
|
||||
dsn: options.dsn,
|
||||
sampleRate: options.sampleRate,
|
||||
transport: EchoSrvTransport,
|
||||
ignoreErrors: [
|
||||
'ResizeObserver loop limit exceeded',
|
||||
'ResizeObserver loop completed',
|
||||
'Non-Error exception captured with keys',
|
||||
],
|
||||
});
|
||||
expect(sentrySetUser).toHaveBeenCalledWith({
|
||||
email: options.user?.email,
|
||||
id: String(options.user?.id),
|
||||
});
|
||||
});
|
||||
|
||||
it('will forward events to transports', async () => {
|
||||
const backend = new SentryEchoBackend(options);
|
||||
backend.transports = [{ sendEvent: jest.fn() }, { sendEvent: jest.fn() }];
|
||||
const event: SentryEchoEvent = {
|
||||
type: EchoEventType.Sentry,
|
||||
payload: { foo: 'bar' } as unknown as SentryEvent,
|
||||
meta: {} as unknown as EchoMeta,
|
||||
};
|
||||
backend.addEvent(event);
|
||||
backend.transports.forEach((transport) => {
|
||||
expect(transport.sendEvent).toHaveBeenCalledTimes(1);
|
||||
expect(transport.sendEvent).toHaveBeenCalledWith(event.payload);
|
||||
});
|
||||
});
|
||||
|
||||
it('integration test with EchoSrv, Sentry and CustomFetchTransport', async () => {
|
||||
// sets up the whole thing between window.onerror and backend endpoint call, checks that error is reported
|
||||
|
||||
// use actual sentry & mock window.fetch
|
||||
const sentry = jest.requireActual('@sentry/browser');
|
||||
(initSentry as jest.Mock).mockImplementation(sentry.init);
|
||||
(sentrySetUser as jest.Mock).mockImplementation(sentry.setUser);
|
||||
const fetchSpy = (window.fetch = jest.fn());
|
||||
fetchSpy.mockResolvedValue({ status: 200 } as Response);
|
||||
|
||||
// set up echo srv & sentry backend
|
||||
const echo = new Echo({ debug: true });
|
||||
setEchoSrv(echo);
|
||||
const sentryBackend = new SentryEchoBackend({
|
||||
...options,
|
||||
dsn: '',
|
||||
customEndpoint: '/log',
|
||||
});
|
||||
echo.addBackend(sentryBackend);
|
||||
|
||||
// lets add another echo backend for sentry events for good measure
|
||||
const myCustomErrorBackend: EchoBackend = {
|
||||
supportedEvents: [EchoEventType.Sentry],
|
||||
flush: () => {},
|
||||
options: {},
|
||||
addEvent: jest.fn(),
|
||||
};
|
||||
echo.addBackend(myCustomErrorBackend);
|
||||
|
||||
// fire off an error using global error handler, Sentry should pick it up
|
||||
const error = new Error('test error');
|
||||
window.onerror!(error.message, undefined, undefined, undefined, error);
|
||||
|
||||
// check that error was reported to backend
|
||||
await waitFor(() => expect(fetchSpy).toHaveBeenCalledTimes(1));
|
||||
const [url, reqInit]: [string, RequestInit] = fetchSpy.mock.calls[0];
|
||||
expect(url).toEqual('/log');
|
||||
expect((JSON.parse(reqInit.body as string) as SentryEvent).exception!.values![0].value).toEqual('test error');
|
||||
|
||||
// check that our custom backend got it too
|
||||
expect(myCustomErrorBackend.addEvent).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,66 +0,0 @@
|
||||
import { BrowserOptions, init as initSentry, setUser as sentrySetUser } from '@sentry/browser';
|
||||
import { FetchTransport } from '@sentry/browser/dist/transports';
|
||||
|
||||
import { BuildInfo } from '@grafana/data';
|
||||
import { SentryConfig } from '@grafana/data/src/types/config';
|
||||
import { EchoBackend, EchoEventType } from '@grafana/runtime';
|
||||
|
||||
import { CustomEndpointTransport } from './transports/CustomEndpointTransport';
|
||||
import { EchoSrvTransport } from './transports/EchoSrvTransport';
|
||||
import { SentryEchoEvent, User, BaseTransport } from './types';
|
||||
|
||||
export interface SentryEchoBackendOptions extends SentryConfig {
|
||||
user?: User;
|
||||
buildInfo: BuildInfo;
|
||||
}
|
||||
|
||||
export class SentryEchoBackend implements EchoBackend<SentryEchoEvent, SentryEchoBackendOptions> {
|
||||
supportedEvents = [EchoEventType.Sentry];
|
||||
|
||||
transports: BaseTransport[];
|
||||
|
||||
constructor(public options: SentryEchoBackendOptions) {
|
||||
// set up transports to post events to grafana backend and/or Sentry
|
||||
this.transports = [];
|
||||
if (options.dsn) {
|
||||
this.transports.push(new FetchTransport({ dsn: options.dsn }, fetch));
|
||||
}
|
||||
if (options.customEndpoint) {
|
||||
this.transports.push(new CustomEndpointTransport({ endpoint: options.customEndpoint }));
|
||||
}
|
||||
|
||||
// initialize Sentry so it can set up its hooks and start collecting errors
|
||||
const sentryOptions: BrowserOptions = {
|
||||
release: options.buildInfo.version,
|
||||
environment: options.buildInfo.env,
|
||||
// seems Sentry won't attempt to send events to transport unless a valid DSN is defined :shrug:
|
||||
dsn: options.dsn || 'https://examplePublicKey@o0.ingest.sentry.io/0',
|
||||
sampleRate: options.sampleRate,
|
||||
transport: EchoSrvTransport, // will dump errors to EchoSrv
|
||||
ignoreErrors: [
|
||||
'ResizeObserver loop limit exceeded',
|
||||
'ResizeObserver loop completed',
|
||||
'Non-Error exception captured with keys',
|
||||
],
|
||||
};
|
||||
|
||||
if (options.user) {
|
||||
sentrySetUser({
|
||||
email: options.user.email,
|
||||
id: String(options.user.id),
|
||||
});
|
||||
}
|
||||
|
||||
initSentry(sentryOptions);
|
||||
}
|
||||
|
||||
addEvent = (e: SentryEchoEvent) => {
|
||||
this.transports.forEach((t) => t.sendEvent(e.payload));
|
||||
};
|
||||
|
||||
// backend will log events to stdout, and at least in case of hosted grafana they will be
|
||||
// ingested into Loki. Due to Loki limitations logs cannot be backdated,
|
||||
// so not using buffering for this backend to make sure that events are logged as close
|
||||
// to their context as possible
|
||||
flush = () => {};
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
import { Event, Severity } from '@sentry/browser';
|
||||
|
||||
import { CustomEndpointTransport } from './CustomEndpointTransport';
|
||||
|
||||
describe('CustomEndpointTransport', () => {
|
||||
const fetchSpy = (window.fetch = jest.fn());
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
// The code logs a warning to console
|
||||
// Let's stub this out so we don't pollute the test output
|
||||
consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
const now = new Date();
|
||||
|
||||
const event: Event = {
|
||||
level: Severity.Error,
|
||||
breadcrumbs: [],
|
||||
exception: {
|
||||
values: [
|
||||
{
|
||||
type: 'SomeError',
|
||||
value: 'foo',
|
||||
},
|
||||
],
|
||||
},
|
||||
timestamp: now.getTime() / 1000,
|
||||
};
|
||||
|
||||
it('will send received event to backend using window.fetch', async () => {
|
||||
fetchSpy.mockResolvedValue({ status: 200 });
|
||||
const transport = new CustomEndpointTransport({ endpoint: '/log' });
|
||||
await transport.sendEvent(event);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
const [url, reqInit]: [string, RequestInit] = fetchSpy.mock.calls[0];
|
||||
expect(url).toEqual('/log');
|
||||
expect(reqInit.method).toEqual('POST');
|
||||
expect(reqInit.headers).toEqual({
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
expect(JSON.parse(reqInit.body!.toString())).toEqual({
|
||||
...event,
|
||||
timestamp: now.toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
it('will back off if backend returns Retry-After', async () => {
|
||||
const rateLimiterResponse = {
|
||||
status: 429,
|
||||
ok: false,
|
||||
headers: new Headers({
|
||||
'Retry-After': '1', // 1 second
|
||||
}),
|
||||
} as Response;
|
||||
fetchSpy.mockResolvedValueOnce(rateLimiterResponse).mockResolvedValueOnce({ status: 200 });
|
||||
const transport = new CustomEndpointTransport({ endpoint: '/log' });
|
||||
|
||||
// first call - backend is called, rejected because of 429
|
||||
await expect(transport.sendEvent(event)).rejects.toEqual(rateLimiterResponse);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
// second immediate call - shot circuited because retry-after time has not expired, backend not called
|
||||
await expect(transport.sendEvent(event)).resolves.toHaveProperty('status', 'skipped');
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
// wait out the retry-after and call again - great success
|
||||
await new Promise((resolve) => setTimeout(() => resolve(null), 1001));
|
||||
await expect(transport.sendEvent(event)).resolves.toBeTruthy();
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('will back off if backend returns Retry-After', async () => {
|
||||
const rateLimiterResponse = {
|
||||
status: 429,
|
||||
ok: false,
|
||||
headers: new Headers({
|
||||
'Retry-After': '1', // 1 second
|
||||
}),
|
||||
} as Response;
|
||||
fetchSpy.mockResolvedValueOnce(rateLimiterResponse).mockResolvedValueOnce({ status: 200 });
|
||||
const transport = new CustomEndpointTransport({ endpoint: '/log' });
|
||||
|
||||
// first call - backend is called, rejected because of 429
|
||||
await expect(transport.sendEvent(event)).rejects.toHaveProperty('status', 429);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
// second immediate call - shot circuited because retry-after time has not expired, backend not called
|
||||
await expect(transport.sendEvent(event)).resolves.toHaveProperty('status', 'skipped');
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
// wait out the retry-after and call again - great success
|
||||
await new Promise((resolve) => setTimeout(() => resolve(null), 1001));
|
||||
await expect(transport.sendEvent(event)).resolves.toBeTruthy();
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('will drop events and log a warning to console if max concurrency is reached', async () => {
|
||||
const calls: Array<(value: unknown) => void> = [];
|
||||
fetchSpy.mockImplementation(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
calls.push(resolve);
|
||||
})
|
||||
);
|
||||
|
||||
const transport = new CustomEndpointTransport({ endpoint: '/log', maxConcurrentRequests: 2 });
|
||||
|
||||
// first two requests are accepted
|
||||
transport.sendEvent(event);
|
||||
const event2 = transport.sendEvent(event);
|
||||
expect(calls).toHaveLength(2);
|
||||
|
||||
// third is skipped because too many requests in flight
|
||||
await expect(transport.sendEvent(event)).resolves.toHaveProperty('status', 'skipped');
|
||||
|
||||
expect(calls).toHaveLength(2);
|
||||
|
||||
// after resolving in flight requests, next request is accepted as well
|
||||
calls.forEach((call) => {
|
||||
call({ status: 200 });
|
||||
});
|
||||
await event2;
|
||||
const event3 = transport.sendEvent(event);
|
||||
expect(calls).toHaveLength(3);
|
||||
calls[2]({ status: 200 });
|
||||
await event3;
|
||||
});
|
||||
});
|
||||
@@ -1,151 +0,0 @@
|
||||
import { Event, Severity } from '@sentry/browser';
|
||||
import { Response } from '@sentry/types';
|
||||
import {
|
||||
logger,
|
||||
makePromiseBuffer,
|
||||
parseRetryAfterHeader,
|
||||
PromiseBuffer,
|
||||
supportsReferrerPolicy,
|
||||
SyncPromise,
|
||||
} from '@sentry/utils';
|
||||
|
||||
import { BaseTransport } from '../types';
|
||||
|
||||
export interface CustomEndpointTransportOptions {
|
||||
endpoint: string;
|
||||
fetchParameters?: Partial<RequestInit>;
|
||||
maxConcurrentRequests?: number;
|
||||
}
|
||||
|
||||
const DEFAULT_MAX_CONCURRENT_REQUESTS = 3;
|
||||
|
||||
const DEFAULT_RATE_LIMIT_TIMEOUT_MS = 5000;
|
||||
|
||||
/**
|
||||
* This is a copy of sentry's FetchTransport, edited to be able to push to any custom url
|
||||
* instead of using Sentry-specific endpoint logic.
|
||||
* Also transforms some of the payload values to be parseable by go.
|
||||
* Sends events sequentially and implements back-off in case of rate limiting.
|
||||
*/
|
||||
|
||||
export class CustomEndpointTransport implements BaseTransport {
|
||||
/** Locks transport after receiving 429 response */
|
||||
private _disabledUntil: Date = new Date(Date.now());
|
||||
|
||||
private readonly _buffer: PromiseBuffer<Response>;
|
||||
|
||||
constructor(public options: CustomEndpointTransportOptions) {
|
||||
this._buffer = makePromiseBuffer(options.maxConcurrentRequests ?? DEFAULT_MAX_CONCURRENT_REQUESTS);
|
||||
}
|
||||
|
||||
sendEvent(event: Event): PromiseLike<Response> {
|
||||
if (new Date(Date.now()) < this._disabledUntil) {
|
||||
const reason = `Dropping frontend event due to too many requests.`;
|
||||
console.warn(reason);
|
||||
return Promise.resolve({
|
||||
event,
|
||||
reason,
|
||||
status: 'skipped',
|
||||
});
|
||||
}
|
||||
|
||||
const sentryReq = {
|
||||
// convert all timestamps to iso string, so it's parseable by backend
|
||||
body: JSON.stringify({
|
||||
...event,
|
||||
level: event.level ?? (event.exception ? Severity.Error : Severity.Info),
|
||||
exception: event.exception
|
||||
? {
|
||||
values: event.exception.values?.map((value) => ({
|
||||
...value,
|
||||
// according to both typescript and go types, value is supposed to be string.
|
||||
// but in some odd cases at runtime it turns out to be an empty object {}
|
||||
// let's fix it here
|
||||
value: fmtSentryErrorValue(value.value),
|
||||
})),
|
||||
}
|
||||
: event.exception,
|
||||
breadcrumbs: event.breadcrumbs?.map((breadcrumb) => ({
|
||||
...breadcrumb,
|
||||
timestamp: makeTimestamp(breadcrumb.timestamp),
|
||||
})),
|
||||
timestamp: makeTimestamp(event.timestamp),
|
||||
}),
|
||||
url: this.options.endpoint,
|
||||
};
|
||||
|
||||
const options: RequestInit = {
|
||||
body: sentryReq.body,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default
|
||||
// https://caniuse.com/#feat=referrer-policy
|
||||
// It doesn't. And it throw exception instead of ignoring this parameter...
|
||||
// REF: https://github.com/getsentry/raven-js/issues/1233
|
||||
referrerPolicy: supportsReferrerPolicy() ? 'origin' : '',
|
||||
};
|
||||
|
||||
if (this.options.fetchParameters !== undefined) {
|
||||
Object.assign(options, this.options.fetchParameters);
|
||||
}
|
||||
|
||||
return this._buffer
|
||||
.add(
|
||||
() =>
|
||||
new SyncPromise<Response>((resolve, reject) => {
|
||||
window
|
||||
.fetch(sentryReq.url, options)
|
||||
.then((response) => {
|
||||
if (response.status === 200) {
|
||||
resolve({ status: 'success' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 429) {
|
||||
const now = Date.now();
|
||||
const retryAfterHeader = response.headers.get('Retry-After');
|
||||
if (retryAfterHeader) {
|
||||
this._disabledUntil = new Date(now + parseRetryAfterHeader(retryAfterHeader, now));
|
||||
} else {
|
||||
this._disabledUntil = new Date(now + DEFAULT_RATE_LIMIT_TIMEOUT_MS);
|
||||
}
|
||||
logger.warn(`Too many requests, backing off till: ${this._disabledUntil}`);
|
||||
}
|
||||
|
||||
reject(response);
|
||||
})
|
||||
.catch(reject);
|
||||
})
|
||||
)
|
||||
.then(undefined, (reason) => {
|
||||
if (reason.message === 'Not adding Promise due to buffer limit reached.') {
|
||||
const msg = `Dropping frontend log event due to too many requests in flight.`;
|
||||
console.warn(msg);
|
||||
return {
|
||||
event,
|
||||
reason: msg,
|
||||
status: 'skipped',
|
||||
};
|
||||
}
|
||||
throw reason;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function makeTimestamp(time: number | undefined): string {
|
||||
if (time) {
|
||||
return new Date(time * 1000).toISOString();
|
||||
}
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function fmtSentryErrorValue(value: unknown): string | undefined {
|
||||
if (typeof value === 'string' || value === undefined) {
|
||||
return value;
|
||||
} else if (value && typeof value === 'object' && Object.keys(value).length === 0) {
|
||||
return '';
|
||||
}
|
||||
return String(value);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { Event } from '@sentry/browser';
|
||||
import { BaseTransport } from '@sentry/browser/dist/transports';
|
||||
import { EventStatus, Request, Session, Response } from '@sentry/types';
|
||||
|
||||
import { getEchoSrv, EchoEventType } from '@grafana/runtime';
|
||||
|
||||
export class EchoSrvTransport extends BaseTransport {
|
||||
sendEvent(event: Event): Promise<{ status: EventStatus; event: Event }> {
|
||||
getEchoSrv().addEvent({
|
||||
type: EchoEventType.Sentry,
|
||||
payload: event,
|
||||
});
|
||||
return Promise.resolve({
|
||||
status: 'success',
|
||||
event,
|
||||
});
|
||||
}
|
||||
// not recording sessions for now
|
||||
sendSession(session: Session): PromiseLike<Response> {
|
||||
return Promise.resolve({ status: 'skipped' });
|
||||
}
|
||||
// required by BaseTransport definition but not used by this implementation
|
||||
_sendRequest(sentryRequest: Request, originalPayload: Event | Session): PromiseLike<Response> {
|
||||
throw new Error('should not happen');
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Event as SentryEvent } from '@sentry/browser';
|
||||
import { Response } from '@sentry/types';
|
||||
|
||||
import { EchoEvent, EchoEventType } from '@grafana/runtime';
|
||||
|
||||
export interface BaseTransport {
|
||||
sendEvent(event: SentryEvent): PromiseLike<Response>;
|
||||
}
|
||||
|
||||
export type SentryEchoEvent = EchoEvent<EchoEventType.Sentry, SentryEvent>;
|
||||
|
||||
export interface User {
|
||||
email: string;
|
||||
id: number;
|
||||
orgId: number;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export const LogMessages = {
|
||||
unknownMessageFromError: 'unknown messageFromError',
|
||||
};
|
||||
|
||||
// logInfo from '@grafana/runtime' should be used, but it doesn't handle Grafana JS Agent and Sentry correctly
|
||||
// logInfo from '@grafana/runtime' should be used, but it doesn't handle Grafana JS Agent correctly
|
||||
export function logInfo(message: string, context: Record<string, string | number> = {}) {
|
||||
if (config.grafanaJavascriptAgent.enabled) {
|
||||
faro.api.pushLog([message], {
|
||||
|
||||
Reference in New Issue
Block a user