package metrics import ( "slices" "strconv" "strings" ) // OtherFormatLabel — значение метки для всего, чего нет в перечне known-форматов. const OtherFormatLabel = "other" // knownFormats — закрытый перечень расширений, которые допускаются меткой. // // Состав: форматы, доезжающие приёмом по HTTP, плюс собственное умолчание // сервиса на случай имени без расширения. Значения `oga` и `mp4` достались от // убранного входа Telegram — голосовое приходило оттуда как `voice/file_N.oga`, // кружок с `.mp4`, — и остаются: перечень сужает **метку**, а не приём, и // выброшенное из него значение уронило бы прежние записи в `other`. var knownFormats = map[string]struct{}{ "mp3": {}, "wav": {}, "ogg": {}, "oga": {}, "opus": {}, "flac": {}, "m4a": {}, "aac": {}, "wma": {}, "mp4": {}, "mkv": {}, "mov": {}, "avi": {}, "webm": {}, "audio": {}, // умолчание сервиса, когда расширения в имени не было } // PublicFormats — тот же перечень, но для подсказки диалогу выбора файла в // приложении: **без** собственного умолчания сервиса. // // Второй перечень рядом с первым разошёлся бы с ним молча, поэтому источник // один. `audio` из него вычтено: это не формат, а умолчание на случай имени без // расширения, и подсказкой человеку оно выходить не должно. // // Сервис по этому перечню **не судит**: приём о годности записи не решает сам — // расширение он берёт из имени файла, а пригодность содержимого узнаёт у // источника метаданных. Перечень служит диалогу выбора файла, не более; норму // держит capability `archive`. func PublicFormats() []string { out := make([]string, 0, len(knownFormats)) for format := range knownFormats { if format == defaultServiceFormat { continue } out = append(out, format) } slices.Sort(out) return out } // defaultServiceFormat — собственное умолчание сервиса на случай имени без // расширения. Форматом не является. const defaultServiceFormat = "audio" // FormatLabel приводит расширение к виду, годному для метки метрики. // // Расширение приходит из имени, которое дал отправитель, и потому может быть // чем угодно: имя `запись.тайное-слово` отдаёт `тайное-слово`. Страница метрик // открыта, то есть метка — поверхность пошире журнала. Незнакомое значение // заменяется одним общим: это закрывает и утечку куска имени, и рост числа // временных рядов, которым иначе распоряжается анонимный отправитель. // // Имени файла на диске это не касается — там расширение остаётся пришедшим. // ObserveInputFileSize записывает размер принятой записи. Расширение приводится // здесь, а не у вызывающего: сырая точка употребления — это место, где хвост // имени отправителя однажды снова уедет наружу, и проверка у вызывающего этого // не заметит. func ObserveInputFileSize(ext string, size int64) { InputFileSizeHistogram.WithLabelValues(FormatLabel(ext)).Observe(float64(size)) } // ObserveConversionDuration записывает длительность конвертации. Исходный формат // приводится по той же причине, что и в приёме. func ObserveConversionDuration(srcExt, targetFormat string, failed bool, seconds float64) { ConversionDurationHistogram. WithLabelValues(FormatLabel(srcExt), targetFormat, strconv.FormatBool(failed)). Observe(seconds) } func FormatLabel(ext string) string { normalized := strings.ToLower(strings.TrimPrefix(ext, ".")) if _, ok := knownFormats[normalized]; ok { return normalized } return OtherFormatLabel }