forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhydro.js
106 lines (90 loc) · 3.13 KB
/
hydro.js
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
import crypto from 'crypto'
import got from 'got'
import statsd from '../lib/statsd.js'
import FailBot from '../lib/failbot.js'
const TIME_OUT_TEXT = 'ms has passed since batch creation'
// If the request to Hydro took an age, it could be either our
// network or that Hydro is just slow to respond. (Event possibly
// too slow to respond with a 422 code)
// If this happens, we'd rather kill it now rather than let it
// linger within the thread.
const POST_TIMEOUT_MS = 3000
export default class Hydro {
constructor({ secret, endpoint } = {}) {
this.secret = secret || process.env.HYDRO_SECRET
this.endpoint = endpoint || process.env.HYDRO_ENDPOINT
}
/**
* Can check if it can actually send to Hydro
*/
maySend() {
return Boolean(this.secret && this.endpoint && process.env.NODE_ENV !== 'test')
}
/**
* Generate a SHA256 hash of the payload using the secret
* to authenticate with Hydro
* @param {string} body
*/
generatePayloadHmac(body) {
return crypto.createHmac('sha256', this.secret).update(body).digest('hex')
}
/**
* Publish a single event to Hydro
* @param {string} schema
* @param {any} value
*/
async publish(schema, value) {
const body = JSON.stringify({
events: [
{
schema,
value: JSON.stringify(value), // We must double-encode the value property
cluster: 'potomac', // We only have ability to publish externally to potomac cluster
},
],
})
const token = this.generatePayloadHmac(body)
const doPost = () =>
got(this.endpoint, {
method: 'POST',
body,
headers: {
Authorization: `Hydro ${token}`,
'Content-Type': 'application/json',
'X-Hydro-App': 'docs-production',
},
// Because we prefer to handle the status code manually below
throwHttpErrors: false,
// The default is no timeout.
timeout: POST_TIMEOUT_MS,
})
const res = await statsd.asyncTimer(doPost, 'hydro.response_time')()
const statTags = [`response_code:${res.statusCode}`]
statsd.increment(`hydro.response_code.${res.statusCode}`, 1, statTags)
statsd.increment('hydro.response_code.all', 1, statTags)
// Track hydro exceptions in Sentry,
// but don't track 5xx because we can't do anything about service availability
if (res.statusCode !== 200 && res.statusCode < 500) {
const err = new Error(
`Hydro request failed: ${res.statusCode} ${res.statusMessage} ${res.body}`
)
err.status = res.statusCode
const failures = res.body
// If Hydro just took too long, ignore it
if (failures.includes(TIME_OUT_TEXT)) throw new Error(`Hydro timed out (${failures})`)
FailBot.report(err, {
hydroStatus: res.statusCode,
hydroText: res.body,
hydroFailures: failures,
})
// If the Hydro request failed as an "Unprocessable Entity", log it for diagnostics
if (res.statusCode === 422) {
console.error(
`Hydro schema validation failed:\n - Request: ${body}\n - Failures: ${failures}`
)
}
throw err
}
return res
}
}