sentry-instrumentation.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /*eslint-env node*/
  2. /*eslint import/no-nodejs-modules:0 */
  3. const crypto = require('crypto');
  4. const https = require('https');
  5. const os = require('os');
  6. const {
  7. NODE_ENV,
  8. SENTRY_INSTRUMENTATION,
  9. SENTRY_WEBPACK_WEBHOOK_SECRET,
  10. GITHUB_SHA,
  11. GITHUB_REF,
  12. } = process.env;
  13. const IS_CI = !!GITHUB_SHA;
  14. const PLUGIN_NAME = 'SentryInstrumentation';
  15. const GB_BYTE = 1073741824;
  16. const createSignature = function (secret, payload) {
  17. const hmac = crypto.createHmac('sha1', secret);
  18. return `sha1=${hmac.update(payload).digest('hex')}`;
  19. };
  20. class SentryInstrumentation {
  21. constructor() {
  22. // Only run if SENTRY_INSTRUMENTATION` is set or when in ci,
  23. // only in the javascript suite that runs webpack
  24. if (!SENTRY_INSTRUMENTATION) {
  25. return;
  26. }
  27. this.initialBuild = false;
  28. this.Sentry = require('@sentry/node');
  29. require('@sentry/tracing'); // This is required to patch Sentry
  30. this.Sentry.init({
  31. dsn: 'https://3d282d186d924374800aa47006227ce9@sentry.io/2053674',
  32. environment: IS_CI ? 'ci' : 'local',
  33. tracesSampleRate: 1.0,
  34. });
  35. if (IS_CI) {
  36. this.Sentry.setTag('branch', GITHUB_REF);
  37. }
  38. const cpus = os.cpus();
  39. this.Sentry.setTag('platform', os.platform());
  40. this.Sentry.setTag('arch', os.arch());
  41. this.Sentry.setTag(
  42. 'cpu',
  43. cpus && cpus.length ? `${cpus[0].model} (cores: ${cpus.length})}` : 'N/A'
  44. );
  45. }
  46. /**
  47. * Measures the file sizes of assets emitted from the entrypoints
  48. */
  49. measureAssetSizes(compilation) {
  50. if (!SENTRY_WEBPACK_WEBHOOK_SECRET) {
  51. return;
  52. }
  53. [...compilation.entrypoints].forEach(([entrypointName, entry]) =>
  54. entry.chunks.forEach(chunk =>
  55. chunk.files
  56. .filter(assetName => !assetName.endsWith('.map'))
  57. .forEach(assetName => {
  58. const asset = compilation.assets[assetName];
  59. const size = asset.size();
  60. const file = assetName;
  61. const body = JSON.stringify({
  62. file,
  63. entrypointName,
  64. size,
  65. commit: GITHUB_SHA,
  66. environment: IS_CI ? 'ci' : '',
  67. node_env: NODE_ENV,
  68. });
  69. const req = https.request({
  70. host: 'product-eng-webhooks-vmrqv3f7nq-uw.a.run.app',
  71. path: '/metrics/webpack/webhook',
  72. method: 'POST',
  73. headers: {
  74. 'Content-Type': 'application/json',
  75. 'Content-Length': Buffer.byteLength(body),
  76. 'x-webpack-signature': createSignature(
  77. SENTRY_WEBPACK_WEBHOOK_SECRET,
  78. body
  79. ),
  80. },
  81. });
  82. req.end(body);
  83. })
  84. )
  85. );
  86. }
  87. measureBuildTime(startTime, endTime) {
  88. if (!this.Sentry) {
  89. return;
  90. }
  91. const transaction = this.Sentry.startTransaction({
  92. op: 'webpack-build',
  93. name: !this.initialBuild ? 'initial-build' : 'incremental-build',
  94. description: 'webpack build times',
  95. startTimestamp: startTime,
  96. trimEnd: true,
  97. });
  98. const span = transaction.startChild({
  99. op: 'build',
  100. description: 'webpack build',
  101. data: {
  102. os: `${os.platform()} ${os.arch()} v${os.release()}`,
  103. memory: os.freemem()
  104. ? `${os.freemem() / GB_BYTE} / ${os.totalmem() / GB_BYTE} GB (${
  105. (os.freemem() / os.totalmem()) * 100
  106. }% free)`
  107. : 'N/A',
  108. loadavg: os.loadavg(),
  109. },
  110. startTimestamp: startTime,
  111. });
  112. span.finish(endTime);
  113. transaction.finish();
  114. }
  115. apply(compiler) {
  116. compiler.hooks.done.tapAsync(
  117. PLUGIN_NAME,
  118. async ({compilation, startTime, endTime}, done) => {
  119. // Only record this once and only on Travis
  120. // Don't really care about asset sizes during local dev
  121. if (IS_CI && !this.initialBuild) {
  122. this.measureAssetSizes(compilation);
  123. }
  124. if (this.Sentry) {
  125. this.measureBuildTime(startTime / 1000, endTime / 1000);
  126. await this.Sentry.flush();
  127. }
  128. this.initialBuild = true;
  129. done();
  130. }
  131. );
  132. }
  133. }
  134. module.exports = SentryInstrumentation;