Pull to refresh
7
0.5
Виктор Поморцев @SpiderEkb

Консультант направления по разработке

Send message

Я бы в такой ситуации попробовал бы логгер запустить отдельным процессом с открытием локального именованного UDP Unix-сокета (ну или что там больше по душе - пайпы те же...). А все остальные поток или процессы просто закидывали бы в это сокет данные для логирования.

А там уже пусть логгер выгребает эти данные, делает с ними что надо и куда надо пишет.

При этом внутри процессов-источников сообщений для лога никаких мьютексов не будет. Просто запись в сокет (который фактически будет входящей очередью логгера).

Хм, а не смотрели на какие-то промышленные логеры, типа log4j/logback (из мира Java).

Нет. Мы работаем с ядром АБС. Платформа IBM i, основной язык - RPG. Java не используется потому что и близко не дает ни той производительности, ни той эффективности по ресурсам.

Там достаточно хорошо проработаны подходы к организации логов, работа с пайплайном и так далее.

Логи хранятся в таблицах БД. Это удобно во всех отношениях. Намного удобнее текста.

Кроме того, мы можем выводить логи с очередь сообщений (message queue, *MSGQ). Это отдельный тип объекта на нашей платформе, туда можно выводить, например, трейсы. В этом случае при работающей программе весь трейсинг можно видеть в реальном времени просто подключившись в другом задании к очереди сообщений.

Обычно выделяют логгер (часть системы, которая публикует логи), форматтер (преобразование логов к нужному формату), лог-коллектор, лог-транспорт, хранилище логов.

Логгер - это несколько функций (фактически, 3-4). Под разные задачи могут быть разные логгеры т.к. специфика выводимой информации может быть разной. Бывает необходимость вообще в лог выводить полный образ записи в БД на которой произошла ошибка.

Более того, нам не нужны единые логи для всего. У нас порядка 200 "программных комплексов", около 30 000 "программных объектов". И каждая команда сама занимается логированием "своих" комплексов - чужие им не интересны. И выводит туда ту информацию, которая нужна им для расследования возможных инцидентов. И/или ту, что хочет видеть бизнес.

Форматтер вообще не нужен. Логи хранятся в БД и работать с ними можно обычным SQL запросом.

Вот была задачка где идет поиск неких соответствий между 96 000 000 строк с одной стороны и 8 000 строк с другой. И если включить трейсинг, то объем информации в логе просто астрономический. Но. При соответсвующей организации лога обычным SQL запросом очень легко отфильтровывается именно тот участок трейснга, который интересен. И никакой форматтер тут просто не нужен.

Или, как пример, прочистка лога

with 
  LOGSET as (
    select A.EC0VAL PGM,
           coalesce(C.EC0VAL, 'D002') DEPT,
           coalesce(D.EC0VAL, 'M001') DEPB,
           coalesce(E.EC0VAL, 'M001') DEPE
      from EC0PF A
 left join EC0PF C on (C.EC0GRP, C.EC0PRM, C.EC0ACT) = (A.EC0GRP, 'LOGDEEPT', 'Y')
 left join EC0PF D on (D.EC0GRP, D.EC0PRM, D.EC0ACT) = (A.EC0GRP, 'LOGDEEPB', 'Y')
 left join EC0PF E on (E.EC0GRP, E.EC0PRM, E.EC0ACT) = (A.EC0GRP, 'LOGDEEPE', 'Y')
     where A.EC0PRM = 'PGMNAME'
       and A.EC0ACT = 'Y'
  ),

  DEPTH as (
    select PGM,
           substr(DEPT, 1, 1) UNTT, 
           dec(substr(DEPT, 2, 3), 3) CNTT,
           substr(DEPB, 1, 1) UNTB,
           dec(substr(DEPB, 2, 3), 3) CNTB,
           substr(DEPE, 1, 1) UNTE,
           dec(substr(DEPE, 2, 3), 3) CNTE
      from LOGSET
  ),

  LMTDTES as (
    select PGM,
           case UNTT
             when 'D' then dec(varchar_format(CURRENT_DATE - CNTT days,   'YYYYMMDD'), 8) - 19000000 
             when 'M' then dec(varchar_format(CURRENT_DATE - CNTT months, 'YYYYMMDD'), 8) - 19000000
             when 'Y' then dec(varchar_format(CURRENT_DATE - CNTT years,  'YYYYMMDD'), 8) - 19000000
           end LMTDTET,
           case UNTB
             when 'D' then dec(varchar_format(CURRENT_DATE - CNTB days,   'YYYYMMDD'), 8) - 19000000 
             when 'M' then dec(varchar_format(CURRENT_DATE - CNTB months, 'YYYYMMDD'), 8) - 19000000
             when 'Y' then dec(varchar_format(CURRENT_DATE - CNTB years,  'YYYYMMDD'), 8) - 19000000
           end LMTDTEB,
           case UNTE
             when 'D' then dec(varchar_format(CURRENT_DATE - CNTE days,   'YYYYMMDD'), 8) - 19000000 
             when 'M' then dec(varchar_format(CURRENT_DATE - CNTE months, 'YYYYMMDD'), 8) - 19000000
             when 'Y' then dec(varchar_format(CURRENT_DATE - CNTE years,  'YYYYMMDD'), 8) - 19000000
           end LMTDTEE
      from DEPTH
  )

delete 
  from ECLLOGPF
  join LMTDTES
    on PGM = ECLLOGPGMN
 where (ECLLOGTYPE = 'T' and ECLLOGDT <= LMTDTET) or
       (ECLLOGTYPE = 'B' and ECLLOGDT <= LMTDTEB) or
       (ECLLOGTYPE = 'E' and ECLLOGDT <= LMTDTEE)
;  

Этот скрипт читает настройки глубин хранения разных типов сообщений для всех логируемых программ (которые хранятся в виде M001 - 1 месяц или D003 - 3 дня и т.п.) и удаляет из лога все, для чего истекло время хранения. Скрипт запускается сопровождением автоматически раз в сутки.

Мы еще в 18-м году прорабатывали "единый сервис логирования", но в итоге пришли к тому, что он будет очень громоздкий и неудобный. Проще оказалось разработать набор базовых требований, а дальше на их основе уже каждая команда делает то, что им удобно. Больше скажу - под разные программные комплексы форматы логов могут быть разными. У нас есть ситуации, когда можно включить трейсинг не просто для отдельной программы, но для конкретного пользователя (профайла из под которого программа запущена) и даже трейсить не всю программу, а отдельные функции (точки логирования). И все это задается в настройках, без пересборки. Т.е. если что-то идет не так, можно просто попросить сопровождение "установить такие-то значения для таких-то полей в такой-то таблице, а потом сделать выгрузку таблицы лога с прома".

В итоге у каждой команды есть как минимум готовые шаблоны функций логирования, как максимум - готовые функции в виде "сервисных программ" (*SRVPGM - аналог динамической библиотеки). Оптимально заточенные под специфику конкретных задач.

Эффективное логирование - это не просто "тогда-то и там-то что-то пошло не так", а информация о том, что пошло не так и что было до и что было после. На каких данных? В каком месте?

Как пример - в рассылке приходит выборка из лога по одной задаче. Там куча ошибок "2020" (структурированная ошибка с кодом KSM2020)

KSM2020 Someone else has just added this record. Your update has not been made

возникает при дублировании уникального ключа при попытке добавить запись.

Просто говоришь сопровождению что в таблице настроек лога нужно включить трейсы для программы откуда пришла ошибка в функции где идет запись в таблицу и сделать выгрузку с прома таблицы куда идет запись до запуска программы и таблицы лога после ее запуска. А дальше уже все видно - что пытаемся в таблицу записать? Почему ключ дублируется а мы считаем что запись нужно добавлять, а не изменять?

В итоге быстро нашли неконсистентность данных в нескольких записях. Поправили, все ок.

Но трейсинг там достаточно объемный - значения полей и т.п. А объемы данных с которыми работает программа очень велики. И разгребать все это из текста, как ни форматируй, достаточно муторно и долго. А скулем я сразу делаю выборку на несколько десятков (максимум) записей где все наглядно. Еще и с исходной таблицей сразу связку могу сделать чтобы посмотреть где именно дублирование идет.

Так что нам проще и эффективнее делать те логи, которые ориентированы на наши конкретные задачи, а не использовать что-то "универсально неудобное".

Кстати, еще момент. На предыдущей работе в определенный момент возникла необходимость удаленного просмотра и мониторинга логов. И там делал в модуле логирования функции, позволяющие удаленно запрашивать и получать логи. А также функции, которые в реальном времени дублировали лог-сообщения на удаленный хост по UDP.

Т.е. подключаешься по UDP к модулю логирования и в реальном времени видишь все, что пишется там в лог. С возможность. менять уровень логирования на ходу.

Там просто ситуация была - объекты по всему городу (и в соседних городах), работали в режиме 24/7 (останавливать процесс нельзя) и если возникали какие-то непонятки, то можно было быстро подключиться и посмотреть что там на объекте реально происходит в реальном времени.

Ситуация, конечно, специфическая, но бывают и такие случаи.

Можно несколько концептуальных мыслей? На основе нашего опыта.

Мы давно уже пришли к необходимости обязательного логирования. И сейчас встраиваем его везде. Поскольку вся работа у нас идет с БД, то логи пишутся также в БД - отдельные таблицы (иногда еще используются очереди сообщений, но это уже чисто платформенная фишка). Но суть не в этом.

Мы пришли к следующим требованиям:

  • Уровень логирования должен задаваться в конфигурации, а не на этапе сборки. Более того, если какой-то модуль предполагает длительную работу, то конфигурация желательно "теплая" - указывается "время жизни" через которое она перечитывается. Это позволяет "на ходу" включать или выключать нужный уровень логирования.
    Конфигурация, это опять же, таблица в БД.

  • У каждого уровня логирования есть свое время жизни сообщений в логе. Например, ошибки хранятся неделю (этого вполне достаточно для проведения расследований - мониторинг логов идет постоянно, статистика по логам рассылается ежедневно те, то отвечает за данный модуль). Бизнес сообщения могут хранится, скажем, 3-6 месяцев (если того пожелает бизнес). Трейсы хранятся 1-3 дня - это оперативная информация, больше не нужно.
    Соответственно, есть функция прочистки лога которая читает конфигурацию и удаляет из лога то, у чего закончился срок жизни. Иногда это не функция, а просто SQL скрипт, ежедневно запускаемый сопровождением в указанное время.

  • Лог может быть один на весь комплекс (много модулей). И тогда логирование для каждого модуля настраивается отдельно.

  • Также пришли к тому, что нужно в лог записывать "точку логирования" - откуда именно (по коду) в лог пришло данное конкретное сообщение. Сильно упрощает работу над ошибками. Точка логирования может быть передана в явном виде (произвольный текст), если нет, то определяется автоматически по колл-стеку - откуда была вызвана функция логирования.

Фактически у нас все сводится к нескольким функциям:

  • LogMessage - вывод в лог сообщения в свободном формате

  • LogError - вывод в лог "структурированной ошибки" (это уже платформозависмое - тут используется такая концепция ошибки в виде структуры из кода ошибки + набора данных, есть специальные "message file" где на каждый код есть текстовка ошибки с указанием куда в текст подставляются данные).

  • AllowLog - "легкая" функция, которая возвращает разрешено ли (в конфигурации) логирование данного уровня сообщения для данной программы. Дело в том, что LogMessage (которая используется для бизнес-сообщений и трейсов) может быть достаточно "тяжелой":
    ECLLogText(MP_PGM_NAME: dsLogKey: 'T':
    'Закрыта существующая запись для совпадения с ' + dsELC01.ELCCUS + dsELC01.ELCCLC + ' ' +
    dsELC01.ELCPRIM +
    ' с EVT = ' + dsELC01.ELCEVT);
    на склейку строки уходит много времени и ресурсов, но если в настоящее время трейсы выключены, это просто пустая трата времени. Поэтому такие вещи оборачиваются в
    if ECLAllowLog(MP_PGM_NAME: 'T');
    ECLLogText(MP_PGM_NAME: dsLogKey: 'T':
    'Закрыта существующая запись для совпадения с ' +
    dsELC01.ELCCUS + dsELC01.ELCCLC + ' ' +
    dsELC01.ELCPRIM +
    ' с EVT = ' + dsELC01.ELCEVT);
    endif;

  • ClearLog - прочистка лога от старых сообщений

Вот как-то так... Такой вот опыт.

Если хотите помочь - ну предлагайте действительно хорошее решение. А не вот это вот с массивами.

Просто представьте, что вам надо развернуть список из 10 000 000 элементов...

Два поста с оптимальным (по простоте и эффективности) решением - оба с минусами.

// head -> node1 -> node2 -> node3 -> nullptr
reverseList(Node* head)
{
  // это будет это будет нового списка
  // newHead = head
  Node* newHead = head;
  
  // следующий после головного узел
  // headNext = node1
  Node* headNext = head->next;
  
  // временная переменная
  Node* tmp;
  
  // этот узел будет последним с развернутом списке
  // head -> nullptr
  head->next = nullptr;
  
  while (headNext != nullptr) {
    // запомним указатель "через узел"
    // 1 tmp = node2
    // 2 tmp = node3
    // 3 tmp = nullptr
    tmp = headNext->next;
    
    // прицепляем головной узел к следующему за ним
    // 1 node1 -> head -> nullptr
    // 2 node2 -> node1 -> head -> nullptr
    // 3 node3 -> node2 -> node1 -> head -> nullptr
    headNext->next = newHead;
    
    // делаем следующий после начала узел "головным"
    // 1 newHead = node1
    // 2 newHead = node2
    // 3 newHead = node3
    newHead = headNext;
    
    // теперь то, что было "через узел", становится следующим
    // 1 headNext = node2
    // 2 headNext = node3
    // 3 headNext = nullptr
    headNext = tmp;
  };
  
  return newHead;
}

Тоже самое на С. Тут комментариев больше чем кода. Просто. Оптимально. Минимум накладных расходов. Минимум кода (больше комментариев).

Все это решается в голове. Просто представляете что у вас есть картонные квадратики с номером узла и стрелочкой, выстроенные в определенном порядке (по стрелочкам). И начинаете их двигать так, чтобы порядок поменялся на противоположный.

P.S. до этого такую задачу не решал, написал первое, что в голову пришло пока читал. Потом уже увидел что такое решение уже было предложено.

P.P.S. сама по себе задача бессмысленная. Если вам нужно ходить по списку туда-сюда - есть двусвязные списки. С необходимостью разворачивать односвязный список за 30+ лет практики не сталкивался ни разу. Хотя со списками приходилось работать много в самых разных их реализациях.

Это уже какой-то ограниченный map, т.к. тип на выходе должен совпадать с типом на входе.

Я вам больше скажу. Бывают случаи, когда нужен не map, а action - некоторое действие с каждым элементов коллекции.

map/filter работают с (потенциально бесконечными) итераторами, а не коллекциями.

Ну это уже сферические map/filter в вакууме. В реальности все ограничивается размерами памяти и накладными расходами на ее выделение/освобождение (когда объемы данных достаточно велики и оценить заранее их не представляется возможным)

Я не спорю и не критикую, просто мысленно примеряю под те задачи, с которыми приходится сталкиваться чаще всего - насколько все это может быть удобно и применимо лично мне. Не более того.

Так много что придумано. Вопрос в реализации...

Ну ситуации бывают разные. Я часто сталкивался с тем, что требуется именно модификация в рамках того же размера памяти. Или фильтрация когда порядок не важен, но важно уменьшение количества элементов вектора (play-off алгоритмы когда на каждом из нескольких проходов размер вектора уменьшается и время цикла на каждом проходе сокращается).

В любом случае, подобные вещи достаточно сильно зависят от задачи и не являются универсальными. Представьте что у вас в векторе хотя бы 500 000 элементов (по нашим меркам это относительно немного). И вам нужно последовательно применить map или filter 10-15-20 раз...

  • map не изменяет оригинальный итератор, а создает новый.

А если у нас в исходной коллекции миллионы элементов и начальная коллекция после применения map нас перестанет интересовать?

Нет ли возможности применить map к исходной коллекции, не создавая ее дубля?

Аналогично для filter - не создать новую отфильтрованную коллекцию, но удалить из исходной те элементы, которые не удовлетворяют условиям фильтра.

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

Ну у меня на телефоне особо тяжелого ничего нет. Банковское, мессенджеры, почта, навигация, читалка да пара трекеров спортивных. Это в основном. Так что 6/64 + 256 на карте памяти хватает за глаза.

С тоской вспоминаю Blackberry Z30 - там двухядерный 1.7Ггц проц и 2Гб памяти. И все летало - никаких лагов. Потому что система по-умному разработана. Там даже невозможно было одним приложением всю систему завесить. Потому что RTOS. Да еще микроядерная.

Ну я не говорю что это было чем-то новаторским в Z30 (я не в курсе, может это еще в Z10 или Q серии было...). Просто это было. Переходника в комплекте не было, но кабель microHDMI - HDMI я за какие-то копейки на али купил.

Не сказать чтобы часто пользовался, но как-то было - в отпуске потребовалось быстро документ какой-то поправить. BT клавиатура с тачем (показывал тут фото), телевизор вместо монитора и вот оно тебе и вот. Практически полноценное рабочее место.

При этом телефон, клавиатура (складная, 10х15см с сложенном виде) и кабель помещаются в одном кармане.

Вообще ощущение что есть телефоны, которые делаются для удобства использования их в работе и в жизни, а есть которые ориентированы исключительно на то, чтобы фоточки котиков в инсту постить. Т.е. разная ЦА. Те, кто на фукционал, просто ту и удобство и те, кому нужен статус и "премиальность"

Ну при чем тут selfhosting вообще?

Почему я не могу перекинуть с компа на телефон книжку чтобы почитать иначе, как прогнав ее через облако (и, строго говоря, вы уверены, что никто это облако не контролирует?). Или какие-то сугубо личные фото (ничего аморального или криминального, просто это личные фото) не могу с телефона на комп слить, не задействуя облако?

И, в конце концов, почему я на элементарную операцию должен тратить инет-трафик? Просто чтобы еще и провайдер заработал?

Вся эта политика четко дает вам понять, что за деньги вы не покупаете телефон с полным объемом заявленного функционала, но покупаете возможность использования функционала под контролем производителя. И производитель в любой момент может в одностороннем порядке урезать вам функционал - запретить пользоваться телефоном для оплаты, отключить облако и т.п. И вы ничего с этим сделать не сможете...

Мне, честно говоря, на все эти внешние атрибуты глубоко все равно. Главное - чтобы было удобно и выполняло свои функции. Даже не вспомню сколько лет телефону - Readmi Note 8 Pro. 4? 5? Важно что все задачи выполняет, батарея живая (на полтора-два дня хватает), все нужные приложений работают, оплата телефоном работает.

Для меня это просто инструмент, а не символ успеха.

Угу. А в BlackBerry Z30 у меня еще был отдельный порт microHDMI и можно было любой монитор к телефону подключить. А к USB клавиатуру/мышку.

Ну-ну... Через облако... А если кто-то там решит что вы себя плохо вели и отключит облако в наказание? Или такого никогда не было?

Вся эта зависимость от внешних серверов, которые вы не контролируете, в один момент превратит ваш телефон в просто звонилку за много денег.

Мне вот как-то проще проводом. С любого компа, просто подключаешь телефон и видишь его как обычную флешку. Безо всяких дополнительных программ. Хоть виндовый комп, хоть линуксовый. Хоть есть интернет, хоть нет его.

За полноценной многозадачностью точно не в андроид. Оно там само может задачу работающую в памяти грохнуть (если ее принудительно там не закрепить).

Так что тут надо какой-нибудь линуксфон.

Из того чем пользовался - BBOS10. Вот там все честно. Приложение сворачиваешь в плитку и оно работает в фоне. Это как минимизированное окно в винде. Все такие приложения (плитки) отображаются на отдельном экране (странице). Хоть их 2, хоть из 20. И сама система их не закроет никогда. А если совсем закрыл приложение, то он честно выгружается из памяти. Все просто и понятно.

Там еще обычно два интерфейса - для полного экрана и для плитки (примерно четверть экрана).

Даже при прочтении "наискосок" бросилось в глаза обилие "премиальный", "премиальность" по отношению в обычному телефону. Это действительно так важно?

Во всем остальном - лично моя любовь - BlackBerry на BBOS 10. Система, по уровню продуманности далеко позади оставляющая как Andriod, так и iOS. Построенная на основе RTOS QNX, которая, в свою очередь, изначально разрабатывалась для встраиваемых систем с ограниченными ресурсами. Ну и UI там очень продуманный - управление свайпами там появилось задолго до того, как из перетащили на другие системы (где-то в 12-13гг) . А концепцию HUB так никто и реализовал до сих пор.

Но... Пришлось с нее перейти на андроид в силу отсутствия нужного софта и функционала.

Я не настоящий сварщик, так что редко пользуюсь.

Есть две BT клавиатуры компактных. Одна побольше

Она же как подставка для планшета/телефона.

Вторая поменьше, складная, с тачпадом

Поскольку в голову не приходит использовать телефон для каких-то серьезных дел и ввода текстов большого объема, то вполне хватает. Для серьезного есть комп.

Тут полностью согласен, но, увы, "качество поддержки" никак не учитывается. Опять же, ни в какой, самой "демократической" избирательной системе.

Потому что учитывать "качество" - это вводить заведомо неравенство по весу голосов. По каким признакам? Доход? Уровень образования?

Увы, но тут сложно (если вообще возможно) какой-то критерий сформулировать, такой, чтобы он был справедливым для всех. Вот и получается что власть опирается на общую (достаточно серую) массу, а не на наиболее развитых ее представителей.

1
23 ...

Information

Rating
1,810-th
Location
Екатеринбург, Свердловская обл., Россия
Works in
Date of birth
Registered
Activity