...

Логгер

Тема в разделе "Примеры решений и дополнительных модулей", создана пользователем kirillovykh, 28 май 2024.

Метки:
  1. kirillovykh

    kirillovykh Участник

    Бизнес кейс
    Отлавливать ошибки в методах api и обработчиках событий пользовательских модулей

    Постановка
    Пользовательский модуль для создания логов и приложение для хранения

    Решение
    Основное назначение – логирование в методах api и обработчиках событий пользовательских модулей, т.к. в данных местах не предусмотрено инструментов пользовательской отладки/обработки ошибок. Также возможно использовать в решении для дебага.

    Состав решения:
    • Приложение Журнал логов. Рекомендуется создавать приложение в специализированном для администрирования разделе. Например, создать раздел «Сервисный раздел».
    [​IMG]
    • Модуль Логгер
    [​IMG]
    [​IMG]

    Описание решения:
    Контекст приложения Журнал логовов:
    • Описание – текст сообщения журнала
    • Уровень – категория уровня логирования
    • Связанный элемент приложения – произвольное приложение
    • Поставщик – категория поставщика логирования
    Контекст настроек модуля:
    • Сообщение журнала – приложение типа Журнал логов
    • Настройки – таблица, свойства – Поставщик (строка), Уровень/Включено – таблица с названием уровня логирования и чекбоксом

    Таблица настроек модуля с минимальной кастомизацией, формируется динамически на основе значений категорий Уровень и Поставщик приложения Журнал логов. Тем самым отредактировав значения категорий, достаточно зайти в настройки модуля, включить необходимые уровни логирования и сохранить.
    [​IMG]
    Код:
    
    async function onInit(): Promise<void> {
        const 
    sets_table Context.fields.sets_table.create();

        for (
    let location of Context.fields.log_item.app.fields.location.data.variants) {
            const 
    row sets_table.insert();
            const 
    old_row Context.data.sets_table?.find(=> x.location === location.code);

            
    row.location location.code;

            if (!
    old_row) {
                const 
    table Context.fields.sets_table.fields.level_sets_table.create();
                
    Context.fields.log_item.app.fields.level.data.variants.map(=> table.insert().level x.code);
                
    row.level_sets_table table;

                continue;
            }

            for (
    let level of Context.fields.log_item.app.fields.level.data.variants) {
                const 
    level_row row.level_sets_table.insert();
                const 
    old_level_row old_row.level_sets_table.find(=> x.level === level.code);

                
    level_row.level level.code;

                if (
    old_level_row) {
                    
    level_row.enabled old_level_row.enabled;
                }
            }
        }

        
    Context.data.sets_table sets_table;
    }

    Дополнительно возможно динамически создавать дерево папок приложения, для удобства хранения логов.

    Для корректного создания лога в теле запроса необходимо указать location, level и message, необязательный параметр ref_item.
    Обработка тела запроса:
    1. Проверка корректности полученных данных
    2. Проверка включения уровня лога для полученного местонахождения
    3. Создание элемента приложения Журнал логов
    4. При ошибке создания отправка сообщения в ленту связанного элемента приложения при его наличии.

    Листинг логгера
    Код:
    
    /**
    * Произвольное приложение
    */
    type MetaRefItem = {
        -
    readonly [K in keyof RefItem as NonFunctionKeys<RefItemK>]: RefItem[K]
    };
    type NonFunctionKeys<Textends keyof T> = T[K] extends () => any never K;

    type Location keyof typeof Namespace.params.fields.log_item.app.fields.location.variants;
    type Level keyof typeof Namespace.params.fields.log_item.app.fields.level.variants;
    type LogData = {
        
    locationLocation,
        
    levelLevel,
        
    messagestring,
        
    ref_item?: MetaRefItem,
    }
    type LogLevelData = {
        
    locationLocation,
        
    messagestring,
        
    ref_item?: MetaRefItem,
    }
    type LogDataKeys keyof LogData;
    type LogLevelDataKeys keyof LogLevelData;


    const 
    logger_app = Namespace.params.fields.log_item.app;
    let log_dataLogData undefined;


    async function log(reqHttpApiRequest): Promise<HttpResponse void> {
        try {
            if (
    typeof req?.body !== 'string') {
                throw new 
    Error('Empty request body');
            }

            
    log_data JSON.parse(req.body);

            if (!
    isLogData(log_data)) {
                throw new 
    Error('Not Log Message');
            }

            if (!
    isEnabled(log_data.locationlog_data.level)) {
                return;
            }

            
    await createLogItem(log_data.locationlog_data.levellog_data.messagelog_data.ref_item);
        }
        catch (
    err) {
            
    await createLogItem('logger''error', `${err.message}: log_data: ${JSON.stringify(log_data)}`, log_data?.ref_item);
        }
    }

    //#region Log by level
    async function logError(reqHttpApiRequest): Promise<HttpResponse void> {
        try {
            if (
    typeof req?.body !== 'string') {
                throw new 
    Error('Empty request body');
            }

            
    log_data JSON.parse(req.body);

            if (!
    isLogLevelData(log_data)) {
                throw new 
    Error('Not Log Level Message');
            }

            if (!
    isEnabled(log_data.location'error')) {
                return;
            }

            
    await createLogItem(log_data.location'error'log_data.messagelog_data.ref_item);
        }
        catch (
    err) {
            
    await createLogItem('logger''error', `${err.message}${logError.name}_data: ${JSON.stringify(log_data)}`, log_data?.ref_item);
        }
    }

    async function logWarning(reqHttpApiRequest): Promise<HttpResponse void> {
        try {
            if (
    typeof req?.body !== 'string') {
                throw new 
    Error('Empty request body');
            }

            
    log_data JSON.parse(req.body);

            if (!
    isLogLevelData(log_data)) {
                throw new 
    Error('Not Log Level Message');
            }

            if (!
    isEnabled(log_data.location'error')) {
                return;
            }

            
    await createLogItem(log_data.location'warn'log_data.messagelog_data.ref_item);
        }
        catch (
    err) {
            
    await createLogItem('logger''error', `${err.message}${logWarning.name}_data: ${JSON.stringify(log_data)}`, log_data?.ref_item);
        }
    }

    async function logInformation(reqHttpApiRequest): Promise<HttpResponse void> {
        ...
    }

    async function logDebug(reqHttpApiRequest): Promise<HttpResponse void> {
        ...
    }
    //#endregion

    //#region Supporting methods
    async function createLogItem(locationLocationlevelLevelmessagestringref_item?: MetaRefItem) {
        try {
            const 
    log_item logger_app.create();

            
    log_item.data.location = Namespace.params.fields.log_item.app.fields.location.variants[location];
            
    log_item.data.level = Namespace.params.fields.log_item.app.fields.level.variants[level];
            
    log_item.data.description message;
            
    log_item.data.application_item ref_item as TRefItem;
            
    log_item.data.status = Namespace.params.fields.log_item.app.fields.status.variants.unprocessed;

            
    await log_item.save();
        }
        catch (
    err) {
            if (!
    ref_item) {
                return;
            }

            const 
    ref_item_obj = new RefItem(ref_item.namespace, ref_item.coderef_item.id);
            const 
    itemApplicationItem<anyany> = await ref_item_obj.fetch();

            
    await item.sendMessage(`Error: Writing ${level} log message`, `${err.message}: log_data: ${JSON.stringify(log_data)}`);
        }
    }

    function 
    isEnabled(locationLocationlevelLevel) {
        return !!Namespace.
    params.data.sets_table.find(=> x.location == location && x.level_sets_table.find(=> y.level == level)?.enabled);
    }

    function 
    isLogData(valueany): value is LogData {
        if (!
    isObject(value)) {
            return 
    false;
        }

        const 
    keysLogDataKeys[] = ['location''level''message'];
        const 
    obj_keys Object.keys(value);

        if (
    keys.some(=> obj_keys.indexOf(x) == -|| !value[x])) {
            return 
    false;
        }

        if (!
    isLocation(value[keys[0]]) || !isLevel(value[keys[1]])) {
            return 
    false;
        }

        return 
    true;
    }

    function 
    isLogLevelData(valueany): value is LogLevelData {
        if (!
    isObject(value)) {
            return 
    false;
        }

        const 
    keysLogLevelDataKeys[] = ['location''message'];
        const 
    obj_keys Object.keys(value);

        if (
    keys.some(=> obj_keys.indexOf(x) == -|| !value[x])) {
            return 
    false;
        }

        if (!
    isLocation(value[keys[0]])) {
            return 
    false;
        }

        return 
    true;
    }

    function 
    isObject(valueany) {
        return 
    Object.prototype.toString.call(value) === '[object Object]';
    }

    function 
    isLocation(valueany): value is Location {
        switch (
    value as Location) {
            case 
    'all':
            case 
    'out_messaging':
            case 
    'in_messaging':
            case 
    'deals':
                return 
    true;
            default:
                return 
    false;
        }
    }

    function 
    isLevel(valueany): value is Level {
        switch (
    value as Level) {
            case 
    'error':
            case 
    'warn':
            case 
    'info':
            case 
    'debug':
            case 
    'log':
                return 
    true;
            default:
                return 
    false;
        }
    }
    //#endregion

    Для методов настроена внешняя авторизация, поэтому при создании запроса необходимо указывать токен пользователя.
    Пример создания лога
    Код:
    
    async function logError(messagestringevent?: CstmEvent) {
        const 
    bodyLogLevelData = {
            
    location: Namespace.params.fields.log_item.app.fields.location.variants.out_messaging.code,
            
    messagemessage,
            
    ref_itemevent?.__item,
        };

        const 
    response await fetch(`${System.getBaseUrl()}/api/extensions/018e9932-69e4-7e20-86fd-cdfb34f17eab/script/log_error`, {
            
    method'POST',
            
    headers: {
                
    'Authorization': `Bearer ${Namespace.params.data.logger_token}`,
            },
            
    bodyJSON.stringify(body),
        });

        if (
    response.status 300) {
            return;
        }

        if (!
    event?.__item) {
            return;
        }

        const 
    ref_item = new RefItem(event.__item.namespace, event.__item.codeevent.__item.id);
        const 
    itemApplicationItem<anyany> = await ref_item.fetch();

        
    await item.sendMessage(
            
    'Error: Writing log message',
            `${
    message} at ${logError.name}\n at ${Namespace.code}:\n response: ${response.status} ${await response.text()}, log_data: ${JSON.stringify(body)}, event: ${JSON.stringify(event)}`
        );
    }

    Последнее редактирование: 28 май 2024