8000 Add basic Sentry tracing instrumentation by dcramer · Pull Request #4 · dcramer/panelkit · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add basic Sentry tracing instrumentation #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Fa 8000 iled to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
"@mdi/react": "^1.4.0",
"@mdi/svg": "^5.2.45",
"@mdi/util": "^0.3.2",
"@sentry/browser": "^5.15.5",
"@sentry/integrations": "^5.15.5",
"@sentry/apm": "^5.17.0",
"@sentry/browser": "^5.17.0",
"@sentry/integrations": "^5.17.0",
"@testing-library/jest-dom": "^5.7.0",
"@testing-library/react": "^10.0.4",
"@testing-library/user-event": "^10.3.1",
Expand Down Expand Up @@ -63,5 +64,8 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@sentry/types": "^5.17.0"
}
}
15 changes: 7 additions & 8 deletions src/components/PanelKit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import styled from "styled-components";
import { ToastContainer, toast } from "react-toastify";
import { Flex, Box } from "reflexbox/styled-components";
import * as Sentry from "@sentry/browser";
import { CaptureConsole as CaptureConsoleIntegration } from "@sentry/integrations";

import "react-toastify/dist/ReactToastify.css";
import "../Toast.css";
Expand All @@ -13,6 +12,7 @@ import HomeAssistant from "../hass";
import EventManager from "./EventManager";
import Header from "./Header";
import TileErrorBoundary from "./TileErrorBoundary";
import { setupSentry } from "../sentry";

const Container = styled.div``;

Expand Down Expand Up @@ -44,14 +44,13 @@ export default class PanelKit extends Component<Props, State> {
const sentryDsn =
process.env.REACT_APP_SENTRY_DSN || this.props.config.sentryDsn;
if (sentryDsn) {
console.log(`[sentry] Initialized with DSN: ${sentryDsn}`);
Sentry.init({
setupSentry({
dsn: sentryDsn,
integrations: [
new CaptureConsoleIntegration({
levels: ["warn", "error"],
}),
],
release: process.env.REACT_APP_GIT_SHA,
environment: process.env.NODE_ENV || "development",
});
Sentry.configureScope((scope) => {
scope.setTransaction("panelkit.boot");
});
}

Expand Down
45 changes: 42 additions & 3 deletions src/hass.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { Entity } from "./types";
import { startSpan } from "./sentry";
import { Span, SpanStatus } from "@sentry/apm";

enum State {
DISCONNECTED = "DISCONNECTED",
Expand All @@ -13,6 +15,19 @@ enum Phase {

const DEFAULT_TIMEOUT = 250; // ms

const ERROR_MAP: {
[code: number]: SpanStatus;
} = {
// default
0: SpanStatus.UnknownError,
// A non-increasing identifier has been supplied.
1: SpanStatus.InvalidArgument,
// Received message is not in expected format (voluptuous validation error).
2: SpanStatus.InvalidArgument,
// Requested item cannot be found
3: SpanStatus.NotFound,
};

const makeSubscriberId = (): string => {
const s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
Expand Down Expand Up @@ -59,6 +74,7 @@ export interface MessageResult {
result: any;
error: {
message: string;
code?: number;
} | null;
success: boolean;
[key: string]: any;
Expand Down Expand Up @@ -89,7 +105,7 @@ export default class HomeAssistant {
private _messageCounter: number = 1;
private _hasPrepared: boolean = false;
private _shouldReconnect: boolean = false;
private _pendingRequests: Map<number, [Function, Function]>;
private _pendingRequests: Map<number, [Function, Function, Span | undefined]>;
private _pendingChanges: Map<number, SuggestedChanges>;
private _eventSubscribers: EventSubscriber[];
private _rootSubscriptions: Map<string, string>;
Expand Down Expand Up @@ -209,10 +225,17 @@ export default class HomeAssistant {
switch (payload.type) {
case "auth_required":
if (this.accessToken) {
// auth requires _no_ ID, which means we can't correlate the message directly
const span = startSpan({
op: "sendCommand",
description: "auth",
});
this.sendMessage({
type: "auth",
access_token: this.accessToken,
});
// TODO(dcramer): this should finish only after auth response
if (span) span.finish();
} else {
console.error("[hass] No authentication token configured");
this.disconnect();
Expand All @@ -239,14 +262,22 @@ export default class HomeAssistant {
if (!promiseHandler) {
console.warn("[hass] No pending request found for event", payload.id);
} else {
let [resolve, reject] = promiseHandler;
let [resolve, reject, span] = promiseHandler;
this._pendingRequests.delete(payload.id);
if (payload.success) {
if (span) {
span.setStatus(SpanStatus.Ok);
}
setTimeout(() => {
this._pendingChanges.delete(payload.id);
}, 1000);
resolve(payload);
} else if (payload.error) {
if (span) {
span.setStatus(
ERROR_MAP[payload.error.code || 0] || SpanStatus.UnknownError
);
}
const error = new Error(payload.error.message);
(error as any).payload = payload;
const changes = this._pendingChanges.get(payload.id);
Expand All @@ -258,6 +289,10 @@ export default class HomeAssistant {
}
reject(error);
}

if (span) {
span.finish();
}
}
break;
default:
Expand Down Expand Up @@ -429,8 +464,12 @@ export default class HomeAssistant {
suggestedChanges: SuggestedChanges | null = null
): CancellablePromise<MessageResult> {
const id = this._messageCounter;
const span = startSpan({
op: "sendCommand",
description: message.type,
});
const promise = new Promise((resolve, reject) => {
this._pendingRequests.set(id, [resolve, reject]);
this._pendingRequests.set(id, [resolve, reject, span]);
if (suggestedChanges) {
this._pendingChanges.set(id, suggestedChanges);
// Object.keys(suggestedChanges).forEach((entityId) => {
Expand Down
54 changes: 54 additions & 0 deletions src/sentry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as Sentry from "@sentry/browser";
import {
Integrations as ApmIntegrations,
Span,
Transaction,
} from "@sentry/apm";
import { CaptureConsole as CaptureConsoleIntegration } from "@sentry/integrations";

const Tracing = ApmIntegrations.Tracing;

export const setupSentry = (options: { [key: string]: any }) => {
console.log(`[sentry] Initialized with DSN: ${options.dsn}`);
Sentry.init({
tracesSampleRate: 1.0,
...options,
integrations: [
new CaptureConsoleIntegration({
levels: ["error"],
}),
new ApmIntegrations.Tracing(),
],
});
};

export const startSpan = (options: {
description?: string;
op?: string;
}): Span | undefined => {
const tracingIntegration = Sentry.getCurrentHub().getIntegration(Tracing);
if (!tracingIntegration) {
console.warn("startSpan called without tracing integration");
return undefined;
}
const transaction = (tracingIntegration as any).constructor.getTransaction();
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its not ideal that we have to attach a span to a transaction (aka we cant always return a span)

if (!transaction) {
console.info("startSpan called without transaction");
return undefined;
}
return transaction.startChild(options);
};

export const startTransaction = (options: {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesnt actually seem to work well.. to some degree i wanted to manually bind a transaction but the tracing integration couples them to itself, and theres no real APIs afaik to manage them without the idle helper

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

damn - didnt find that one :(

name: string;
op?: string;
description?: string;
}): Transaction | undefined => {
const hub = Sentry.getCurrentHub();
const tracingIntegration = hub.getIntegration(Tracing);
if (!tracingIntegration) {
console.warn("startTransaction called without tracing integration");
return undefined;
}
return Tracing.startIdleTransaction(options);
};
4 changes: 2 additions & 2 deletions src/tiles/AlarmTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ type AlarmTileProps = TileProps & {
};

export default class AlarmTile extends Tile<AlarmTileProps> {
=> {
class="x x-first x-last">async () => {
const { state } = this.getEntity(this.props.entityId);
if (ARMED_STATES.has(state)) {
this.callService(
await this.callService(
"alarm_control_panel",
"alarm_disarm",
{
Expand Down
4 changes: 2 additions & 2 deletions src/tiles/AutomationTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export default class AutomationTile extends Tile<AutomationTileProps> {

static defaultIcon = "home-automation";

=> {
this.callService("automation", this.props.action, {
class="x x-first x-last">async () => {
await this.callService("automation", this.props.action, {
entity_id: this.props.entityId,
});
};
Expand Down
2 changes: 1 addition & 1 deletion src/tiles/CameraTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default class CameraTile extends Tile<CameraTileProps, CameraTileState> {
});
};

=> {
class="x x-first x-last">async () => {
this.openModal();
};

Expand Down
2 changes: 1 addition & 1 deletion src/tiles/DoorControlTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type DoorControlTileProps = TileProps & {
export default class DoorControlTile extends Tile<DoorControlTileProps> {
static defaultIcon = "door";

=> {
class="x x-first x-last">async () => {
this.openModal();
};

Expand Down
4 changes: 2 additions & 2 deletions src/tiles/FanTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ type FanTileProps = TileProps & {
};

export default class FanTile extends Tile<FanTileProps> {
=> {
class="x x-first x-last">async () => {
const { state } = this.getEntity(this.props.entityId);
this.callService(
await this.callService(
"fan",
state === "on" ? "turn_off" : "turn_on",
{
Expand Down
2 changes: 1 addition & 1 deletion src/tiles/InputSelectTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type InputSelectTileProps = TileProps & {
};

export default class InputSelectTile extends Tile<InputSelectTileProps> {
=> {
class="x x-first x-last">async () => {
const {
state,
attributes: { options },
Expand Down
6 changes: 3 additions & 3 deletions src/tiles/LightTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ type LightTileProps = TileProps & {
};

export default class LightTile extends Tile<LightTileProps> {
=> {
class="x x-first x-last">async () => {
const { state } = this.getEntity(this.props.entityId);
this.callService(
await this.callService(
"light",
state === "on" ? "turn_off" : "turn_on",
{
Expand All @@ -22,7 +22,7 @@ export default class LightTile extends Tile<LightTileProps> {
);
};

=> {
class="x x-first x-last">async () => {
this.openModal();
};

Expand Down
4 changes: 2 additions & 2 deletions src/tiles/LockTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ type LockTileProps = TileProps & {
};

export default class LockTile extends Tile<LockTileProps> {
=> {
class="x x-first x-last">async () => {
const { state } = this.getEntity(this.props.entityId);
this.callService(
await this.callService(
"lock",
state === "locked" ? "unlock" : "lock",
{
Expand Down
4 changes: 2 additions & 2 deletions src/tiles/SceneTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ type SceneTileProps = TileProps & {
};

export default class SceneTile extends Tile<SceneTileProps> {
=> {
this.callService("scene", "turn_on", {
class="x x-first x-last">async () => {
await this.callService("scene", "turn_on", {
entity_id: this.props.entityId,
});
};
Expand Down
4 changes: 2 additions & 2 deletions src/tiles/ScriptTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ type ScriptTileProps = TileProps & {
export default class ScriptTile extends Tile<ScriptTileProps> {
static defaultIcon = "script";

=> {
class="x x-first x-last">async () => {
let [domain, service] = this.props.entityId.split(".", 2);
this.callService(domain, service, this.props.data);
await this.callService(domain, service, this.props.data);
};

renderTitle() {
Expand Down
4 changes: 2 additions & 2 deletions src/tiles/SwitchTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export default class SwitchTile extends Tile<SwitchTileProps> {
}
}

=> {
class="x x-first x-last">async () => {
const { state } = this.getEntity(this.props.entityId);
this.callService(
await this.callService(
"switch",
state === "on" ? "turn_off" : "turn_on",
{
Expand Down
Loading
0