Skip to content
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

Refactor symonClient initiate function #1175

Merged
merged 11 commits into from
Nov 15, 2023
39 changes: 23 additions & 16 deletions src/components/probe/prober/http/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,24 @@
* SOFTWARE. *
**********************************************************************************/

import { BaseProber, NotificationType } from '..'
import { getContext } from '../../../../context'
import events from '../../../../events'
import { getEventEmitter } from '../../../../utils/events'
import { httpRequest } from './request'
import { BaseProber, NotificationType } from '..'
import type { ProbeAlert } from '../../../../interfaces/probe'
import {
type ProbeRequestResponse,
probeRequestResult,
type ProbeRequestResponse,
type RequestConfig,
} from '../../../../interfaces/request'
import type { ProbeAlert } from '../../../../interfaces/probe'
import responseChecker from '../../../../plugins/validate-response/checkers'
import { logResponseTime } from '../../../logger/response-time-log'
import { saveProbeRequestLog } from '../../../logger/history'
import type { ValidatedResponse } from '../../../../plugins/validate-response'
import responseChecker from '../../../../plugins/validate-response/checkers'
import { getAlertID } from '../../../../utils/alert-id'
import { getEventEmitter } from '../../../../utils/events'
import { isSymonModeFrom } from '../../../config'
import { startDowntimeCounter } from '../../../downtime-counter'
import { saveProbeRequestLog } from '../../../logger/history'
import { logResponseTime } from '../../../logger/response-time-log'
import { httpRequest } from './request'

type ProbeResultMessageParams = {
request: RequestConfig
Expand Down Expand Up @@ -185,6 +186,15 @@ export class HTTPProber extends BaseProber {
requestIndex: number,
triggeredAlert: ProbeAlert
) {
const probeID = this.probeConfig.id
const url = this.probeConfig?.requests?.[requestIndex].url || ''
const validation = {
alert: triggeredAlert,
isAlertTriggered: true,
response,
}
const alertId = getAlertID(url, validation, probeID)

getEventEmitter().emit(events.probe.alert.triggered, {
probe: this.probeConfig,
requestIndex,
Expand All @@ -193,18 +203,15 @@ export class HTTPProber extends BaseProber {

startDowntimeCounter({
alert: triggeredAlert,
probeID: this.probeConfig.id,
url: this.probeConfig?.requests?.[requestIndex].url || '',
probeID,
url,
})

this.sendNotification({
requestURL: this.probeConfig?.requests?.[requestIndex].url || '',
requestURL: url,
notificationType: NotificationType.Incident,
validation: {
alert: triggeredAlert,
isAlertTriggered: true,
response,
},
validation,
alertId,
})

this.logMessage(
Expand Down
66 changes: 42 additions & 24 deletions src/components/probe/prober/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,30 @@
**********************************************************************************/

import type { Notification } from '@hyperjumptech/monika-notification'
import { type Incident, getContext } from '../../../context'
import { getContext, type Incident } from '../../../context'
import events from '../../../events'
import type { Probe, ProbeAlert } from '../../../interfaces/probe'
import {
probeRequestResult,
type ProbeRequestResponse,
} from '../../../interfaces/request'
import { FAILED_REQUEST_ASSERTION } from '../../../looper'
import type { ValidatedResponse } from '../../../plugins/validate-response'
import { getAlertID } from '../../../utils/alert-id'
import { getEventEmitter } from '../../../utils/events'
import { log } from '../../../utils/pino'
import { isSymonModeFrom } from '../../config'
import { sendAlerts } from '../../notification'
import { saveNotificationLog, saveProbeRequestLog } from '../../logger/history'
import { logResponseTime } from '../../logger/response-time-log'
import type { ValidatedResponse } from '../../../plugins/validate-response'
import {
startDowntimeCounter,
stopDowntimeCounter,
} from '../../downtime-counter'
import { FAILED_REQUEST_ASSERTION } from '../../../looper'
import {
DEFAULT_INCIDENT_THRESHOLD,
DEFAULT_RECOVERY_THRESHOLD,
} from '../../config/validation/validator/default-values'
import {
startDowntimeCounter,
stopDowntimeCounter,
} from '../../downtime-counter'
import { saveNotificationLog, saveProbeRequestLog } from '../../logger/history'
import { logResponseTime } from '../../logger/response-time-log'
import { sendAlerts } from '../../notification'

export type ProbeResult = {
isAlertTriggered: boolean
Expand All @@ -62,6 +63,7 @@ type SendNotificationParams = {
requestURL: string
notificationType: NotificationType
validation: ValidatedResponse
alertId: string
}

export interface Prober {
Expand Down Expand Up @@ -240,8 +242,8 @@ export class BaseProber implements Prober {
protected throwIncidentIfNeeded(
incidentRetryAttempt: number,
incidentThreshold: number = DEFAULT_INCIDENT_THRESHOLD,
message: string = 'Probing failed'
) {
message = 'Probing failed'
): void {
const isIncidentThresholdMet =
incidentRetryAttempt === incidentThreshold - 1

Expand All @@ -262,6 +264,7 @@ export class BaseProber implements Prober {
requestURL,
notificationType,
validation,
alertId,
}: SendNotificationParams): Promise<void> {
const isRecoveryNotification = notificationType === NotificationType.Recover
getEventEmitter().emit(events.probe.notification.willSend, {
Expand All @@ -270,6 +273,7 @@ export class BaseProber implements Prober {
url: requestURL,
probeState: isRecoveryNotification ? ProbeState.Up : ProbeState.Down,
validation,
alertId,
})

if (!this.hasNotification()) {
Expand Down Expand Up @@ -337,14 +341,21 @@ export class BaseProber implements Prober {
error: requestResponse.errMessage,
})

const url = this.probeConfig?.requests?.[requestIndex].url || ''
const validation = {
alert: failedRequestAssertion,
isAlertTriggered: true,
response: requestResponse,
}
const probeID = this.probeConfig.id

const alertId = getAlertID(url, validation, probeID)

this.sendNotification({
requestURL: this.probeConfig?.requests?.[requestIndex].url || '',
requestURL: url,
notificationType: NotificationType.Incident,
validation: {
alert: failedRequestAssertion,
isAlertTriggered: true,
response: requestResponse,
},
validation: validation,
alertId,
}).catch((error) => log.error(error.mesage))
}

Expand All @@ -366,14 +377,21 @@ export class BaseProber implements Prober {
url: this.probeConfig?.requests?.[requestIndex].url || '',
})

const url = this.probeConfig?.requests?.[requestIndex].url || ''
const validation = {
alert: recoveredIncident.alert,
isAlertTriggered: false,
response: probeResults[requestIndex].requestResponse,
}
const probeID = this.probeConfig.id

const alertId = getAlertID(url, validation, probeID)

this.sendNotification({
requestURL: this.probeConfig?.requests?.[requestIndex].url || '',
requestURL: url,
notificationType: NotificationType.Recover,
validation: {
alert: recoveredIncident.alert,
isAlertTriggered: false,
response: probeResults[requestIndex].requestResponse,
},
validation: validation,
alertId,
}).catch((error) => log.error(error.mesage))
}

Expand Down
2 changes: 1 addition & 1 deletion src/events/subscribers/probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
* SOFTWARE. *
**********************************************************************************/

import events from '../../events'
import type { Notification } from '@hyperjumptech/monika-notification'
import events from '../../events'
import type { StatuspageNotification } from '../../plugins/visualization/atlassian-status-page'
import { AtlassianStatusPageAPI } from '../../plugins/visualization/atlassian-status-page'
import { getEventEmitter } from '../../utils/events'
Expand Down
6 changes: 3 additions & 3 deletions src/symon/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ import { rest } from 'msw'
import { setupServer } from 'msw/node'
import sinon from 'sinon'

import type { Config } from '../interfaces/config'
import type { MonikaFlags } from '../flag'
import type { Config } from '../interfaces/config'

import * as loggerHistory from '../components/logger/history'
import { setContext } from '../context'
import SymonClient from '.'
import { validateProbes } from '../components/config/validation'
import * as loggerHistory from '../components/logger/history'
import { setContext } from '../context'

let getUnreportedLogsStub: sinon.SinonStub

Expand Down
64 changes: 25 additions & 39 deletions src/symon/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ import path from 'path'
import Piscina from 'piscina'

import { updateConfig } from '../components/config'
import { validateProbes } from '../components/config/validation'
import { getOSName } from '../components/notification/alert-message'
import { getContext } from '../context'
import { SYMON_API_VERSION, type MonikaFlags } from '../flag'
import events from '../events'
import { SYMON_API_VERSION, type MonikaFlags } from '../flag'
import { Config } from '../interfaces/config'
import { Probe } from '../interfaces/probe'
import { ValidatedResponse } from '../plugins/validate-response'
Expand All @@ -48,7 +49,6 @@ import {
publicIpAddress,
publicNetworkInfo,
} from '../utils/public-ip'
import { validateProbes } from '../components/config/validation'

type SymonHandshakeData = {
city: string
Expand Down Expand Up @@ -82,6 +82,7 @@ type NotificationEvent = {
probeState: string
url: string
validation: ValidatedResponse
alertId: string
}

type ConfigListener = (config: Config) => void
Expand Down Expand Up @@ -218,43 +219,8 @@ class SymonClient {
this.eventEmitter = getEventEmitter()
this.eventEmitter.on(
events.probe.notification.willSend,
({ probeID, probeState, url, validation }: NotificationEvent) => {
const getAlertID = ({
url,
validation,
}: Pick<NotificationEvent, 'url' | 'validation'>): string => {
if (validation.alert.id) {
return validation.alert.id
}

const probe = getContext().config?.probes.find(
({ id }) => id === probeID
)
if (!probe) {
return ''
}

const request = probe.requests?.find((request) => request.url === url)
if (!request) {
return ''
}

return request.alerts?.find((alert) => alert.query === '')?.id || ''
}

this.notifyEvent({
alertId: getAlertID({ url, validation }),
event: probeState === 'DOWN' ? 'incident' : 'recovery',
response: {
body: validation.response.data,
headers: validation.response.headers || {},
size: validation.response.headers['content-length'],
status: validation.response.status, // status is http status code
time: validation.response.responseTime,
},
}).catch((error: unknown) => {
log.error(error)
})
({ probeState, validation, alertId }: NotificationEvent) => {
this.willSendNotification(probeState, validation, alertId)
}
)

Expand All @@ -270,6 +236,26 @@ class SymonClient {
this.onConfig((config) => updateConfig(config, false))
}

willSendNotification(
probeState: string,
validation: ValidatedResponse,
alertId: string
): void {
this.notifyEvent({
alertId,
event: probeState === 'DOWN' ? 'incident' : 'recovery',
response: {
body: validation.response.data,
headers: validation.response.headers || {},
size: validation.response.headers['content-length'],
status: validation.response.status, // status is http status code
time: validation.response.responseTime,
},
}).catch((error: unknown) => {
log.error(error)
})
}

async notifyEvent(event: SymonClientEvent): Promise<void> {
log.debug('Sending incident/recovery event to Symon')
await this.httpClient.post('/events', { monikaId: this.monikaId, ...event })
Expand Down
48 changes: 48 additions & 0 deletions src/utils/alert-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**********************************************************************************
* MIT License *
* *
* Copyright (c) 2021 Hyperjump Technology *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to deal *
* in the Software without restriction, including without limitation the rights *
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in all *
* copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
* SOFTWARE. *
**********************************************************************************/

import { getContext } from '../context'
import type { ValidatedResponse } from '../plugins/validate-response'

export function getAlertID(
url: string,
validation: ValidatedResponse,
probeID: string
): string {
if (validation.alert.id) {
return validation.alert.id
}

const probe = getContext().config?.probes.find(({ id }) => id === probeID)
if (!probe) {
return ''
}

const request = probe.requests?.find((request) => request.url === url)
if (!request) {
return ''
}

return request.alerts?.find((alert) => alert.query === '')?.id || ''
}
Loading