Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save yayanet/fc4557f833d85a7ac8a8c91d1c35b098 to your computer and use it in GitHub Desktop.
Save yayanet/fc4557f833d85a7ac8a8c91d1c35b098 to your computer and use it in GitHub Desktop.
Sentry Cleanup: Debug Information Files
const axios = require('axios')
const http = require('http')
const LinkHeader = require('http-link-header')
const MAX_REQUESTS_COUNT = 20
const INTERVAL_MS = 10
let PENDING_REQUESTS = 0
const nextPage = (header) => {
const link = LinkHeader.parse(header)
const [ next ] = link.get('rel', 'next')
return next && next.results === 'true' ? next.uri : null
}
function uri (strings, ...values) {
const escapeValue = (value, index) => strings[index] + encodeURIComponent(value)
return values.map(escapeValue).join('') + strings[strings.length - 1]
}
// https://docs.sentry.io/api/
class SentryClient {
constructor ( { url, timeout, name, token, org } ) {
if (url.slice(-1) === '/') {
url = url.slice(0, -1)
}
this.org = org
this.client = axios.create({
baseURL: `${url}/api/0`,
timeout: timeout || 30000,
headers: {
'Authorization': `Bearer ${token}`,
'User-Agent': name || 'sentry-client',
},
httpAgent: new http.Agent({ keepAlive: true }),
})
/**
* Axios Request Interceptor
*/
this.client.interceptors.request.use(function (config) {
return new Promise((resolve, reject) => {
let interval = setInterval(() => {
if (PENDING_REQUESTS < MAX_REQUESTS_COUNT) {
PENDING_REQUESTS++
clearInterval(interval)
resolve(config)
}
}, INTERVAL_MS)
})
})
/**
* Axios Response Interceptor
*/
this.client.interceptors.response.use(function (response) {
PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1)
return Promise.resolve(response)
}, function (error) {
PENDING_REQUESTS = Math.max(0, PENDING_REQUESTS - 1)
return Promise.reject(error)
})
}
async request ({ method, url }) {
try {
const response = await this.client.request({ method, url })
// https://docs.sentry.io/api/pagination/
let next = nextPage(response.headers.link || '')
// if (next && next.indexOf('20:100') === -1) {
if (next) {
console.log(`get list: ${next}`)
const _next = await this.request({ method, url: next })
return response.data.concat(_next)
}
return response.data
} catch (error) {
// TODO: properly unpack API errors
if (error.reponse) {
console.error('ERROR processing', method, url)
console.error('HTTP', error.response.status, error.response.statusText)
console.error('BODY', error.response.data)
throw new Error('Nope')
} else {
throw error
}
}
}
// https://docs.sentry.io/api/projects/get-debug-files/
async getDebugFiles({ project }) {
return this.request({
method: 'GET',
url: uri`/projects/${this.org}/${project}/files/dsyms/`,
})
}
// https://docs.sentry.io/api/projects/get-debug-files/
async deleteDebugFile({ project, id }) {
return this.request({
method: 'DELETE',
url: uri`/projects/${this.org}/${project}/files/dsyms/?id=${id}`,
})
}
async getAllDebugFiles({ projects }) {
const promises = projects.map(project => {
return this.getDebugFiles({ project })
})
const filesPerProject = await Promise.all(promises)
const result = []
const now = Date.now()
function addFile (project, item) {
const created = new Date(item.dateCreated)
const since = Math.floor((now - created.getTime()) / 1000 / 3600 / 24)
result.push({ ...item, project, since })
}
projects.forEach((project, index) => {
filesPerProject[index].forEach(item => {
addFile(project, item)
})
})
return result
}
}
// -------------------------------------------------------------
const URL = ''
const ORG = 'sentry'
// create at https://my-sentry-instance.example.org/settings/account/api/auth-tokens/ requesting these scopes
// event:admin, event:read, member:read, org:read, project:read, project:releases, team:read, project:write, project:admin
// no idea if they're all necessary as API docs don't elaborate on that
const TOKEN = ''
const PROJECTS = [ '' ]
// anything older than that will be removed
const DAYS = 180 // 6 months
;(async () => {
const sentry = new SentryClient({
url: URL,
token: TOKEN,
org: ORG,
})
const files = await sentry.getAllDebugFiles({ projects: PROJECTS})
let deletingCount = 0
const removals = files.filter(item => {
// ignore debug information files that are not old enough
return item.since >= DAYS
}).map(item => {
deletingCount++
console.log(`current ${deletingCount} item: id: ${item.id}, uuid: ${item.uuid}, objectName: ${item.objectName}, size: ${item.size}, date: ${item.dateCreated}`)
return sentry.deleteDebugFile({ project: item.project, id: item.id })
})
await Promise.all(removals)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment