Внедрение зависимостей – проще, чем кажется?

    Привет, Хабр!

    У нас готовится к выходу второе издание легендарной книги Марка Симана, «Внедрение зависимостей в .NET»



    Поэтому сегодня мы решили кратко освежить тему внедрения зависимостей для специалистов по .NET и C# и предлагаем перевод статьи Грэма Даунса, где эта парадигма рассматривается в контексте инверсии управления (IoC) и использования контейнеров.

    Большинству программистов, пожалуй, известен феномен «Внедрение зависимостей» (Dependency Injection), но не всем понятно, какой смысл в него вкладывается. Вероятно, вам приходилось иметь дело с интерфейсами и контейнерами, и иногда работа с ними приводила вас в тупик. С другой стороны, вы, возможно, только что-то слышали о внедрении зависимостей, и открыли эту статью, так как хотите лучше разобраться в их сути. В этой статье я покажу, насколько проста концепция внедрения зависимостей, и какой мощной она может быть на самом деле.

    Внедрение зависимостей — это самодостаточный подход, который можно использовать сам по себе. С другой стороны, этот подход можно применять и вместе с интерфейсами, и с контейнерами для внедрения зависимостей/инверсии управления (DI/IoC). Применяя внедрение зависимостей в таком контексте, можно столкнуться с некоторой путаницей, которую поначалу испытывал и я.

    На протяжении всей карьеры (я специализируюсь на разработке в Net/C#), я привык использовать внедрение зависимостей в его чистейшей форме. При этом я реализовывал DI, вообще не прибегая ни к контейнерам, ни к инверсии управления. Все изменилось совсем недавно, когда мне поставили задачу, в которой без использования контейнеров было не обойтись. Тогда я крепко усомнился во всем, что знал ранее.

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

    (Здесь важно отметить: интерфейсы и контейнеры используются только в контексте внедрения зависимостей. Внедрение зависимостей можно реализовать и без интерфейсов/контейнеров, но, в сущности, единственное назначение интерфейсов или контейнеров – облегчить внедрение зависимостей).

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

    Подготовка


    Чтобы лучше понять внедрение зависимостей в их чистейшей форме, давайте разберем пример приложения, написанного на C#.

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

    По ходу работы вы увидите, как требования, предъявляемые к логированию, постепенно усложняются, и мы удовлетворяем эти требования, используя внедрение зависимостей; при этом зона ответственности класса Calculator сводится к минимуму. Внедрение зависимостей также избавит нас от необходимости видоизменять класс Calculator всякий раз, когда мы захотим поменять устройство логирования.

    Приложение


    Рассмотрим следующий код. Он написан для простого приложения-калькулятора, принимающего два числа, оператор и выводящего результат. (Это простое рабочее приложение для командной строки, поэтому вам не составит труда воспроизвести его как C# Console Application в Visual Studio и вставить туда код, если вы хотите следить за развитием примера. Все должно работать без проблем.)

    У нас есть класс Calculator и основной класс Program, использующий его.

    Program.cs:

    using System;
    using System.Linq;
    
    namespace OfferZenDiTutorial
    {
        class Program
        {
            static void Main(string[] args)
            {
                var number1 = GetNumber("Enter the first number: > ");
                var number2 = GetNumber("Enter the second number: > ");
                var operation = GetOperator();
                var calc = new Calculator();
                var result = GetResult(calc, number1, number2, operation);
                Console.WriteLine($"{number1} {operation} {number2} = {result}");
                Console.Write("Press any key to continue...");
                Console.ReadKey();
            }
    
            private static float GetNumber(string message)
            {
                var isValid = false;
                while (!isValid)
                {
                    Console.Write(message);
                    var input = Console.ReadLine();
                    isValid = float.TryParse(input, out var number);
                    if (isValid)
                        return number;
    
                    Console.WriteLine("Please enter a valid number. Press ^C to quit.");
                }
    
                return -1;
            }
    
            private static char GetOperator()
            {
                var isValid = false;
                while (!isValid)
                {
                    Console.Write("Please type the operator (/*+-) > ");
                    var input = Console.ReadKey();
                    Console.WriteLine();
                    var operation = input.KeyChar;
                    if ("/*+-".Contains(operation))
                    {
                        isValid = true;
                        return operation;
                    }
    
                    Console.WriteLine("Please enter a valid operator (/, *, +, or -). " +
                                      "Press ^C to quit.");
                }
    
                return ' ';
            }
    
            private static float GetResult(Calculator calc, float number1, float number2, 
                char operation)
            {
                switch (operation)
                {
                    case '/': return calc.Divide(number1, number2);
                    case '*': return calc.Multiply(number1, number2);
                    case '+': return calc.Add(number1, number2);
                    case '-': return calc.Subtract(number1, number2);
                    default:
                        // Такого произойти не должно, если с предыдущими валидациями все было нормально 
                        throw new InvalidOperationException("Invalid operation passed: " + 
                                                            operation);
                }
            }
        }
    }

    Главная программа запускается, запрашивает у пользователя два числа и оператор, а затем вызывает класс Calculator для выполнения простой арифметической операции над этими числами. Затем выводит результат операции. Вот класс Calculator.

    Calculator.cs:

    namespace OfferZenDiTutorial
    {
        public class Calculator
        {
            public float Divide(float number1, float number2)
            {
                return number1 / number2;
            }
    
            public float Multiply(float number1, float number2)
            {
                return number1 * number2;
            }
    
            public float Add(float number1, float number2)
            {
                return number1 + number2;
            }
    
            public float Subtract(float number1, float number2)
            {
                return number1 - number2;
            }
        }
    }

    Логирование


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

    Кажется, что не так это и сложно, верно? Берете и добавляете инструкции, в соответствии с которыми все операции, производимые в Calculator, должны заноситься в текстовый файл. Вот как теперь выглядит ваш Calculator:

    Calculator.cs:

    using System.IO;
    
    namespace OfferZenDiTutorial
    {
        public class Calculator
        {
            private const string FileName = "Calculator.log";
    
            public float Divide(float number1, float number2)
            {
                File.WriteAllText(FileName, $"Running {number1} / {number2}");
                return number1 / number2;
            }
    
            public float Multiply(float number1, float number2)
            {
                File.WriteAllText(FileName, $"Running {number1} * {number2}");
                return number1 * number2;
            }
    
            public float Add(float number1, float number2)
            {
                File.WriteAllText(FileName, $"Running {number1} + {number2}");
                return number1 + number2;
            }
    
            public float Subtract(float number1, float number2)
            {
                File.WriteAllText(FileName, $"Running {number1} - {number2}");
                return number1 - number2;
            }
        }
    }

    Прекрасно работает. Всякий раз, когда в Calculator что-либо происходит, он записывает это в файл Calculator.log, расположенный в той же директории, откуда он запускается.

    Но, возможен вопрос: а в самом ли деле уместно, чтобы класс Calculator отвечал за запись в текстовый файл?

    Класс FileLogger


    Нет. Разумеется, это не его зона ответственности. Поэтому, чтобы не нарушался принцип единственной ответственности, все, что касается логирования информации, должно происходить в файле логов, и для этого требуется написать отдельный самодостаточный класс. Давайте этим и займемся.

    Первым делом создаем совершенно новый класс, назовем его FileLogger. Вот как он будет выглядеть.

    FileLogger.csh:

    using System;
    using System.IO;
    
    namespace OfferZenDiTutorial
    {
        public class FileLogger
        {
            private const string FileName = "Calculator.log";
            private readonly string _newLine = Environment.NewLine;
    
            public void WriteLine(string message)
            {
                File.AppendAllText(FileName, $"{message}{_newLine}");
            }
        }
    }

    Теперь все, что касается создания файла логов и записи информации в него обрабатывается в этом классе. Дополнительно получаем и одну приятную мелочь: что бы ни потреблял этот класс, не требуется ставить между отдельными записями пустые строки. Записи должны просто вызывать наш метод WriteLine, а все остальное мы берем на себя. Разве не круто?
    Чтобы использовать класс, нам нужен объект, который его инстанцирует. Давайте решим эту проблему внутри класса Calculator. Заменим содержимое класса Calculator.cs следующим:

    Calculator.cs:

    namespace OfferZenDiTutorial
    {
        public class Calculator
        {
            private readonly FileLogger _logger;
    
            public Calculator()
            {
                _logger = new FileLogger();
            }
    
            public float Divide(float number1, float number2)
            {
                _logger.WriteLine($"Running {number1} / {number2}");
                return number1 / number2;
            }
    
            public float Multiply(float number1, float number2)
            {
                _logger.WriteLine($"Running {number1} * {number2}");
                return number1 * number2;
            }
    
            public float Add(float number1, float number2)
            {
                _logger.WriteLine($"Running {number1} + {number2}");
                return number1 + number2;
            }
    
            public float Subtract(float number1, float number2)
            {
                _logger.WriteLine($"Running {number1} - {number2}");
                return number1 - number2;
            }
        }
    }

    Итак, теперь нашему калькулятору не важно, как именно новый логгер записывает информацию в файл, или где находится этот файл, и происходит ли вообще запись в файл. Однако, все равно существует одна проблема: можем ли мы вообще рассчитывать на то, что класс Calculator будет знать, как создается логгер?

    Внедрение зависимости


    Очевидно, ответ на последний вопрос отрицательный!

    Именно здесь, уважаемый читатель, в дело вступает внедрение зависимости. Давайте изменим конструктор нашего класса Calculator:

    Calculator.cs:

            public Calculator(FileLogger logger)
            {
                _logger = logger;
            }

    Вот и все. Больше в классе ничего не меняется.

    Внедрение зависимостей – это элемент более крупной темы под названием «Инверсия управления», но ее подробное рассмотрение выходит за рамки этой статьи.

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

    Итак, чья же это ответственность?

    Как раз того, кто инстанцирует класс Calculator. В нашем случае это – основная программа.

    Чтобы это продемонстрировать, изменим метод Main в нашем классе Program.cs следующим образом:

    Program.cs

      static void Main(string[] args)
            {
                var number1 = GetNumber("Enter the first number: > ");
                var number2 = GetNumber("Enter the second number: > ");
                var operation = GetOperator();
                // Следующие две строки изменены
                var logger = new FileLogger();
                var calc = new Calculator(logger);
                var result = GetResult(calc, number1, number2, operation);
                Console.WriteLine($"{number1} {operation} {number2} = {result}");
                Console.Write("Press any key to continue...");
                Console.ReadKey();
            }

    Таким образом, требуется изменить всего две строки. Мы не рассчитываем, что класс Calculator инстанцирует FileLogger, это за него сделает Main, а затем передаст ему результат.

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

    Расширение возможностей: сделаем другой логгер


    Несмотря на вышесказанное, у интерфейсов есть свое место, и по-настоящему они раскрываются именно в связке с Внедрением Зависимостей.

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

    Как вы считаете, придется ли ради этого делать изменения внутри Calculator, что потенциально потребует перекомпилировать и перераспределить ту сборку, в которой он находится?

    Вот здесь нам и пригодятся интерфейсы.

    Давайте напишем интерфейс. Назовем его ILogger, поскольку его реализацией будет заниматься наш класс FileLogger.

    ILogger.cs

    namespace OfferZenDiTutorial
    {
        public interface ILogger
        {
            void WriteLine(string message);
        }
    }
    

    Как видите, он определяет единственный метод: WriteLine, реализованный FileLogger. Сделаем еще шаг и формализуем эти отношения, сделав так, чтобы этот класс официально реализовывал наш новый интерфейс:

    FileLogger.cs

    public class FileLogger : ILogger

    Это единственное изменение, которое мы внесем в этот файл. Все остальное будет как прежде.
    Итак, отношение мы определили – что нам теперь с ним делать?

    Для начала изменим класс Calculator таким образом, чтобы он использовал интерфейс ILogger, а не конкретную реализацию FileLogger:

    Calculator.cs

    private readonly ILogger _logger;
    
            public Calculator(ILogger logger)
            {
                _logger = logger;
            }

    На данном этапе код по-прежнему компилируется и выполняется без всяких проблем. Мы передаем в него FileLogger из главного метода программы, того, который реализует ILogger. Единственное отличие заключается в том, что Calculator не просто не требуется знать, как создавать FileLogger, но и даже логгер какого рода ему выдается.

    Поскольку все, что бы вы ни получили, реализует интерфейс ILogger (и, следовательно, имеет метод WriteLine), с практическим использованием проблем не возникает.

    Теперь давайте добавим еще одну реализацию интерфейса ILogger. Это будет класс, который ничего не делает при вызове метода WriteLine. Мы назовем его NullLogger, и вот как он выглядит:

    NullLogger.cs

    namespace OfferZenDiTutorial
    {
        public class NullLogger : ILogger
        {
            public void WriteLine(string message)
            {
                // Ничего не делаем в этой реализации
            }
        }
    }

    На этот раз нам вообще ничего не потребуется менять в классе Calculator, если мы соберемся использовать новый NullLogger, поскольку тот уже принимает что угодно, реализующее интерфейс ILogger.

    Нам потребуется изменить только лишь метод Main в нашем файле Program.cs, чтобы передать в него иную реализацию. Давайте этим и займемся, чтобы метод Main принял следующий вид:

    Program.cs

     static void Main(string[] args)
            {
                var number1 = GetNumber("Enter the first number: > ");
                var number2 = GetNumber("Enter the second number: > ");
                var operation = GetOperator();
                var logger = new NullLogger(); // Эту строку нужно изменить
                var calc = new Calculator(logger);
                var result = GetResult(calc, number1, number2, operation);
                Console.WriteLine($"{number1} {operation} {number2} = {result}");
                Console.Write("Press any key to continue...");
                Console.ReadKey();
            }

    Опять же, изменить нужно только ту строку, которую я откомментировал. Если мы хотим использовать иной механизм логирования (например, такой, при котором информация записывается в журнал событий Windows, либо с применением SMS-уведомлений или уведомлений по электронной почте), то нам потребуется всего лишь передать иную реализацию
    интерфейса ILogger.

    Небольшая оговорка об интерфейсах


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

    Существует так называемая «обфускация при помощи абстракции», которая, в сущности, сводится к переусложнению проекта ради реализации всех этих различных уровней. Если хотите, то можете обойтись и без всех этих интерфейсов, если только нет конкретных причин, по которым они вам нужны. Вообще не следует создавать такой интерфейс, у которого будет всего одна реализация.

    Контейнеры для внедрения зависимостей


    Пример, прорабатываемый в этой статье, довольно прост: мы имеем дело с единственным классом, у которого всего одна зависимость. Теперь предположим, что у нас множество зависимостей, и каждая из них связана с другими зависимостями. Даже при работе над умеренно сложными проектами вполне вероятно, что вам придется иметь дело с такими ситуациями, и будет не просто держать в уме, что нужно создать все эти классы, а также помнить, какой из них от какого зависит – особенно, если вы решили пользоваться интерфейсами.

    Знакомьтесь с контейнером для внедрения зависимостей. Он упрощает вам жизнь, но принцип работы такого контейнера может показаться весьма запутанным, особенно, когда вы только начинаете его осваивать. На первый взгляд эта возможность может отдавать некоторой магией.
    В данном примере мы воспользуемся контейнером от Unity, но на выбор есть и много других, назову лишь наиболее популярные: Castle Windsor, Ninject. С функциональной точки зрения эти контейнеры практически не отличаются. Разница может быть заметна на уровне синтаксиса и стиля, но, в конечном итоге, все сводится к вашим персональным предпочтениям и опыту разработки (а также к тому, что предписывается в вашей компании!).

    Давайте подробно разберем пример с использованием Unity: я постараюсь объяснить, что здесь происходит.

    Первым делом вам потребуется добавить ссылку на Unity. К счастью, для этого существует пакет Nuget, поэтому щелкните правой кнопкой мыши по вашему проекту в Visual Studio и выберите Manage Nuget Packages:



    Найдите и установите пакет Unity, ориентируйтесь на проект Unity Container:



    Итак, мы готовы. Измените метод Main файла Program.cs вот так:

    Program.cs

     static void Main(string[] args)
            {
                var number1 = GetNumber("Enter the first number: > ");
                var number2 = GetNumber("Enter the second number: > ");
                var operation = GetOperator();
                // Следующие три строки необходимо изменить
                var container = new UnityContainer();
                container.RegisterType<ILogger, NullLogger>();
                var calc = container.Resolve<Calculator>();
                var result = GetResult(calc, number1, number2, operation);
                Console.WriteLine($"{number1} {operation} {number2} = {result}");
                Console.Write("Press any key to continue...");
                Console.ReadKey();
            }

    Опять же, менять требуется только те строки, которые отмечены. При этом, нам не придется ничего менять ни в классе Calculator, ни в одном из логгеров, ни в их интерфейсе: теперь они все внедряются во время исполнения, поэтому, если нам понадобится иной логгер, то мы должны будем всего лишь изменить конкретный класс, регистрируемый для ILogger.

    При первом запуске этого кода вы можете столкнуться с такой ошибкой:



    Вероятно, это одна из причуд с той версией пакета Unity, которая была актуальна на момент написания этой статьи. Надеюсь, что у вас все пройдет гладко.
    Все дело в том, что при установке Unity также устанавливается неверная версия другого пакета, System.Runtime.CompilerServices.Unsafe. Если вы получаете такую ошибку, то должны вернуться к менеджеру пакетов Nuget, найти этот пакет под вкладкой “Installed” и обновить его до новейшей стабильной версии:



    Теперь, когда этот проект работает, что именно он делает? Когда вы впервые пишете код таким образом, тянет предположить, что здесь замешана какая-то магия. Сейчас я расскажу, что мы здесь видим на самом деле и, надеюсь, это будет сеанс магии с разоблачением, поэтому далее вы не будете остерегаться работы с контейнерами.

    Все начинается со строки var calc = container.Resolve<Calculator>();, поэтому именно отсюда я изложу смысл этого кода в форме «диалога контейнера с самим собой»: о чем он «думает», когда видит эту инструкцию.

    1. “Мне задано разрешить что-то под названием Calculator. Я знаю, что это такое?”
    2. “Вижу, в актуальном дереве процессов есть класс под названием Calculator. Это конкретный тип, значит, у него всего лишь одна реализация. Просто создам экземпляр этого класса. Как выглядят конструкторы?”
    3. “Хм, а конструктор всего один, и принимает он что-то под названием ILogger. Я знаю, что это такое?”
    4. “Нашел, но это же интерфейс. Мне вообще сообщалось, как его разрешать?”
    5. “Да, сообщалось! В предыдущей строке сказано, что, всякий раз, когда мне требуется разрешить ILogger, я должен передать экземпляр класса NullLogger.”
    6. “Окей, значит тут есть NullLogger. У него непараметризованный конструктор. Просто создам экземпляр.”
    7. “Передам этот экземпляр конструктору класса Calculator, а затем верну этот экземпляр к var calc.”

    Обратите внимание: если бы у NullLogger был конструктор, который запрашивал бы дополнительные типы, то конструктор просто повторил бы для них все шаги, начиная с 3. В принципе, он просматривает все типы и пытается автоматически разрешить все найденные типы в конкретные экземпляры.

    Также обратите внимание, что в пункте 2 разработчикам не приходится явно регистрировать конкретные типы. При желании это можно сделать, например, чтобы изменить жизненный цикл либо передать производные классы и т.д. Но это еще одна причина избегать создания интерфейсов, пока в них нет очевидной нужды.

    Вот и все. Ничего таинственного и особо мистического.

    Другие возможности


    Стоит отметить, что контейнеры позволяют делать еще некоторые вещи, которые было бы довольно сложно (но не невозможно) реализовать самостоятельно. Среди таких вещей – управление жизненным циклом и внедрение методов и свойств. Обсуждение этих тем выходит за рамки данной статьи, поскольку, маловероятно, что такие техники понадобятся начинающим. Но я рекомендую вам, освоившись с темой, внимательно почитать документацию, чтобы понимать, какие еще возможности существуют.

    Если вы хотите сами поэкспериментировать с примерами, приведенными в этой статье: смело клонируйте с Гитхаба репозиторий, в котором они выложены github.com/GrahamDo/OfferZenDiTutorial.git. Там семь веток, по одной на каждую рассмотренную нами итерацию.

    Комментарии 28

      0
      Пример крайне неудачный. FileLogger продвигает в явном виде антипаттерн, противоречащий 12 factor apps. А именно — логгирование должно производится только в консоль и никуда более.

      Во-2, у вас один интерфейс и у него одна зависимость. Для этого нужен DI контейнер, серьёзно?
        +3

        Как должен логировать в консоль, например, Windows Service?

          0
          Точно также, только при запуске службы перенаправить stdout из dev/null в системный журнал event log

          Я собственно ни разу не против DI. И в контексте DI считаю вполне нормальным использовать ILogger, предоставляющий для ICalculator возможности логгирования.
            +1

            Как именно можно перенаправить Windows Service StdOut в Event log при запуске службы, следуя принципам двенадцати факторов?
            И почему кстати Event log предпочтительнее иных средств, например, тестовых файлов?

              0
              Элементарно. Запускать бинарник из процесса, который стартует (инсталирует, суспендит, закрывает) службу и выполняет в ней бинарник, при этом перехватывает stdout бинарника и пишет его в системный журнал

              Я не говорил, что он предпочтительнее везде и всегда. Иногда в зависимости от задачи и обстоятельств предпочтительнее может быть что угодно — dev/null, консоль, файл, ELK. Но как правило для windows системный журнал — хороший выбор, потому что это стандартный и рекомендованный вендором системный компонент централизованного логгирования + фронтенд.
                0
                Элементарно. Запускать бинарник из процесса, который стартует (инсталирует, суспендит, закрывает) службу и выполняет в ней бинарник, при этом перехватывает stdout бинарника и пишет его в системный журнал

                Это совсем не элементарно и потому вызывает сомнения в целесообразности.
                Какую проблему решит такой подход по сравнению с изоляцией логики логирования классическими средствами модульности (абстракции, библиотеки)?
                В проектах какой компании можно встретить реализацию такого подхода к Windows Service? Где-то можно почитать об опыте его использования? Ведь надёжное межпроцессное взаимодействие штука не дешёвая.

                  0
                  Код я привёл в другой ветке, он тривиальный. Там требуется ещё garcefull shutdown, но я понятия не имею как это реализуется в windows без сигналов unix.

                  Такой подход позволяет:
                  — при разработке и в рабочем окружении не используются сторонние сервисы и зависимости, связанные с логгированием
                  — разработчику не нужно заниматься маршрутизацией и хранением stdout, это делегируется внешним программам
                  — поскольку поток событий пишется в stdout гарантированно без буферизации, кеширования и прочих вещей, которые могут создавать накладные расходы, у разработчика есть гарантия, что логгирование не станет узким местом, не создаст доп. нагрузку на GC и в случае C# на асинхронный пул

                  «надёжное межпроцессное взаимодействие штука не дешёвая.» — это верно, но не в случае перехвата дочернего stdout родительским процессом, в этом случае всё действительно просто

                  «В проектах какой компании можно встретить реализацию такого подхода к Windows Service?» — не могу сказать. Много ли компаний вообще используют windows service для бизнес кода? не уверен, не видел таких ни разу. В основном для инфраструктурного и системного насколько я могу судить. Разумеется в системном и инфраструктурном программировании 12F подходит не всегда, там много старых гайдланов и различных древних хакерских практик, которые до сих пор в ходу. понятно, что при разработке и работе условного драйвера для видюхи нет практически ничего из того, что предполагает 12F
            0
            В 12 factor apps речь идёт не о консоли, конечно. Постулируется, что «Приложение двенадцати факторов никогда не занимается маршрутизацией и хранением своего потока вывода… Вместо этого каждый выполняющийся процесс записывает свой поток событий без буферизации в стандартный вывод stdout.»
            Если с первой частью я согласен, в пределах её применимости, то вторая, на мой взгляд, ошибочна. В более-менее серьёзном (веб-) сервисе логи структурированные. Помимо текстового сообщения, каждая запись несет в себе дополнительные данные и метаданные. Простым выводом в stdout тут не отделаешься. Нужен промежуточный агент со своим интерфейсом, позволяющий приложению записать структурированное событие и передающий его среде исполнения.
              0
              Ну понятно же, что консоль — это и есть stdout в большинстве случаев.

              == вторая, на мой взгляд, ошибочна.
              == Простым выводом в stdout тут не отделаешься.

              Текстовое сообщение в данном случае — это json с «дополнительными данными и метаданными», для которого ни какие специальные агенты не требуются, а достаточно простой обёртки над выводом в stdut. Инфраструктурное ПО берёт сообщение из stdout и перенаправляет в logstash (или Loki). Смысл постулата — приложение никогда не должно само писать в logstash, loki, или боже упаси напрямую в elasticsearch.

                0
                Нет, консоль — это не stdout. Хотя, действительно, чаще всего stdout перенаправляется на консоль, отождествлять их — это грубая ошибка. Консоль это инфраструктура, включающая в себя экранный буфер, c произвольным доступом, аттрибутами символов и буфер ввода, имеющие свой собственный API. Для приложения выводить на консоль и выводить в stdout — это две большие разницы.
                По поводу «простой обёртки над выводом» — с фактом наличия этой обёртки мы уже определились. Если возражений о том, что у этой обёртки есть набор публичных методов aka API, нет, то у нас получается тот самый логгер, только, скажем, не FileLogger, а StdoutLogger. А дальше, до введения собственно интерфейса ILogger остаётся один шаг.
                Почему нужен агент? — в реальности, среда выполнения и инфраструктура журналирования не тождественны, а в случае с Windows service, приведённом выше, такая инфраструктура журналирования, как описывается в манифесте 12 factor app, просто отсутствует. Вот мы и получаем, с одной стороны — ILogger, с другой — необходимость в промежуточном агенте. Контейнер зависимостей, динамично выстраеваемый под конкретную среду выполнения, хотя и не без серьёзных недостатков, имеет право на существование, как решение этой потребности. Правда, лично мне для .Net больше нравится Apache Log4Net.
                  0
                  Это ни какая не грубая ошибка, а нормальное отождествление наиболее распространённого случая. По умолчанию stdout для разработчика — это консоль практически всегда.
                  А дальше, до введения собственно интерфейса ILogger остаётся один шаг.
                  А я изначально ничего и не имел против ILogger
                  в случае с Windows service, приведённом выше, такая инфраструктура журналирования, как описывается в манифесте 12 factor app, просто отсутствует
                  Элементарно создаётся на пальцах. Докер. nssm.cc Наверняка есть и что-то ещё, просто вы не в курсе
                  Контейнер зависимостей, динамично выстраеваемый под конкретную среду выполнения, хотя и не без серьёзных недостатков, имеет право на существование, как решение этой потребности.
                  Нет. Логгер нужно передавать в каждую функцию/метод в качестве параметра, чтобы получить в сообщении предопределённый сверху стека вызовов набор ключ-значение
                    0
                    Это ни какая не грубая ошибка, а нормальное отождествление наиболее распространённого случая. По умолчанию stdout для разработчика — это консоль практически всегда.


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

                    Элементарно создаётся на пальцах. Докер. nssm.cc Наверняка есть и что-то ещё, просто вы не в курсе

                    Да, конечно, давайте посадим Windows service в докер-контейнер, со всеми его накладными расходами, и побъёмся над тем, чтобы обеспечить нужное поведение. Есть такая штука — целесообразность применения — наверное, Вы просто не в курсе.
                    А теперь давайте взглянем с точки зрения манифеста 12. Среда исполнения Windows service — это таки Windows, со своим, особым взаимодействием с сервисами. То, что Вы предлагаете — это вкрутить ещё одну прослойку между сервисом и средой исполнения так, чтобы приложение можно было уложить в прокрустово ложе манифеста. Ну будет у Вас Docker этим самым агентом. Всё равно ничего не изменится.

                    Нет. Логгер нужно передавать в каждую функцию/метод в качестве параметра, чтобы получить в сообщении предопределённый сверху стека вызовов набор ключ-значение

                    Я так понимаю, это желание выдать частное, и спорное архитектурное решение за общий случай? Какие, простите, ключи и значения необходимо передавать в каждый метод в стеке, и почему их обязательно необходимо оборачивать в логгер?
                      –1
                      явные проблемы в понимании архитектуры.
                      Это вообще не имеет значения. Называйте как хотите. Не надо навязывать другим своё мнение.
                      давайте посадим Windows service в докер-контейнер
                      Эти измышления не уместны здесь. У меня был контраргумент к вашему утверждению «такая инфраструктура журналирования, как описывается в манифесте 12 factor app, просто отсутствует». Очевидно это не верно — я вам показал почему. Вопрос целесообразности я не поднимал, не надо всё мешать в одну кучу. И понимаете вы его не правильно если что. Понятно же, что службы windows разработаны 20-30 лет назад по дремучим гайдлайнам, которые ни кто переделывать не собирается, ни гайдлайны, ни сами службы. Но я с эти и не спорил — не надо приписывать мне то, что у вас в голове. Разработка служб windows — это вообще маргинальная и узкоспециализированная ниша, интересная чуть более чем никому. Какая ниша — такая и инфраструктура, такой и уровень соответствия современным рекомендациям. Когда мне нужно запускать веб-сервис в виде службы windows, я напишу обычный веб-сервис и без проблем запущу его в виде службы и направлю stdout куда мне надо без посторонних «агентов» и докеров. Цена вопроса — ~15-20 строк банального кода. Обсуждать это, тем более об этом спорить — бессмысленно. Не умеете — ни кто вас не заставляет.
                      Какие, простите, ключи и значения необходимо передавать в каждый метод в стеке,
                      Любые. Прочитайте об общих понятиях структурного логгирования прежде чем задавать подобные вопросы.
                      и почему их обязательно необходимо оборачивать в логгер?
                      Потому что они относятся к логгированию и ни к чему более. Но опять таки ни кто вам не мешает предавать ключ-значение напрямую, без ILogger, или вообще не использовать коль вы не умеете в структурное логгирование.
                        0
                        Это вообще не имеет значения. Называйте как хотите. Не надо навязывать другим своё мнение.


                        Это публичный форум, я высказываю свое мнение, и имею на это право. Вы имеете полное право с ним не соглашаться, поэтому ни о каком навязывании речи не идёт.

                        Очевидно это не верно

                        Это, очевидно, верно, и, именно поэтому потребовалось втащить в дискуссию Docker. И Вы же с этим согласны: «Какая ниша — такая и инфраструктура, такой и уровень соответствия современным рекомендациям».

                        Вопрос целесообразности я не поднимал, не надо всё мешать в одну кучу. И понимаете вы его не правильно если что.

                        Спасибо за диагноз, так сказать, по фотографии, давайте Вы уж тогда поясните критерии целесообразности вот этого: «Запускать бинарник из процесса, который стартует службу и выполняет в ней бинарник, при этом перехватывает stdout бинарника и пишет его в системный журнал», и вот этого в контексте Windows service: Элементарно создаётся на пальцах. Докер. nssm.cc .

                        Когда мне нужно запускать веб-сервис в виде службы windows, я напишу обычный веб-сервис и без проблем запущу его в виде службы и направлю stdout куда мне надо без посторонних «агентов» и докеров. Цена вопроса — ~15-20 строк банального кода.

                        Научиться чему-то новому — всегда полезно, серьёзно. Если это можно сделать в 15-20 строк кода — с интересном изучу. Опубликуете?

                        Любые. Прочитайте об общих понятиях структурного логгирования прежде чем задавать подобные вопросы.

                        Это уход от ответа. Если Вы разбираетесь в этом хорошо, совершенно несложно привести конкретный пример.

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

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

                        Ну и напоследок добавлю: да, Windows services это очень старая инфраструктура. Ну а Azure Application Services, Azure Functions, etc.? Тоже объявим дремучими и маргинальными?
                          0
                          Вы имеете полное право с ним не соглашаться, поэтому ни о каком навязывании речи не идёт.
                          А давайте тогда я буду говорить «консоль», а вы не будете меня одёргивать.
                          Это, очевидно, верно,
                          Я привёл вам факты, которые опровергаю ваше утверждение.
                          Спасибо за диагноз, так сказать, по фотографии
                          При чём здесь диагноз?
                          тогда поясните критерии целесообразности вот этого
                          Поясню. У меня логии === коснсоль. И, наоборот, всё, что попадает в консоль, автоматически идёт в логи. При разработке я думаю не о том, как логгировать, а о том, что логгировать. Могу вообще не использовать ILogger, а просто выводить в консоль нужный текст. Решение о том, как и куда логгировать — по умолчанию в консоль (в 99 % случаев), в никуда, в файл, базу данных, logstash — принимается при запуске программы, а не в процессе разработки. При этом мне для логгирования наплевать, в каком окружении работает мой сервис, в amazon cloude или в виде службы windows — мне не нужно менять программу, чтобы адаптировать её под произвольный журнал.
                          Если это можно сделать в 15-20 строк кода — с интересном изучу. Опубликуете?
                          Зачем? Это элементарные вещи. Вы не можете запустить дочерний процесс или редиректнуть его stdout? Вам в SO тогда наверное, там этого много для любого ЯП
                          совершенно несложно привести конкретный пример.
                          погуглил за вас
                          логгер у нас уже хранит состояние, плюс поддерживает многопоточную запись. Уже есть сомнения в возможности " достаточно простой обёртки над выводом в stdut"
                          То есть любой конкурентный код у вас по определению не может быть простым? Ну пусть будет так — простой для меня и сложной для вас обёртки. В любом случае речи не шло о том, чтобы писать логгер самому, готовых решение более чем достаточно
                          Ну а Azure Application Services, Azure Functions, etc.? Тоже объявим дремучими и маргинальными?
                          Не знаю. На основании чего их следуете такими объявить? Вообще по отзывам админов/девопсов azure cloud — проприетарщина и отвратительное соотношение цена/качество, и не понятно кто его использует и зачем. Может некие «партнёры» по специальным «программам».
                            0
                            А давайте тогда я буду говорить «консоль», а вы не будете меня одёргивать.

                            Давайте Вы будете называть поток вывода как Вам удобно, и не будете мне затыкать рот, когда я высказываю по этому поводу своё мнение?

                            При разработке я думаю не о том, как логгировать, а о том, что логгировать.

                            Это, очевидно, не так. Вы думаете исключительно о том, _как_ логгировать в поток вывода, и, именно поэтому Вам приходится изобретать способ обернуть своё консольное приложение так, чтобы оно выглядело как Windows service, наплевав при этом на производительность, отказоустойчивость, простоту обслуживания. И Вы прекрасно иллюстрируете, почему постулирование записи в поток вывода — это ошибка.
                            Зона ответственности приложения заканчивается на вызове методов ILogger.Log*whatever*. Что происходит с логами дальше — это уже не забота разработчика.

                            При этом мне для логгирования наплевать, в каком окружении работает мой сервис, в amazon cloude или в виде службы windows — мне не нужно менять программу, чтобы адаптировать её под произвольный журнал.

                            Запись логов в поток вывода не является необходимым условием для этого. Более того, Вам не наплевать — Вам приходится выкручиваться, чтобы приспособить среду выполнения к бесмысленному требованию.

                            Зачем? Это элементарные вещи.

                            Хорошо, всё понятно. Вы — не можете.

                            погуглил за вас

                            Спасибо, ссылка не имеет абсолютно никакого отношения к вопросу, и примера у Вас нет. Кстати, Вы заметили, как ловко автор заметает под ковёр вопрос конвертации даты? :)

                            То есть любой конкурентный код у вас по определению не может быть простым? Ну пусть будет так — простой для меня и сложной для вас обёртки.

                            Безусловно, правильная реализация многопоточности — это уже непростой код, а шапкозакидательство не свидетельство квалификации. Обычно, наоборот.

                            Не знаю… Вообще по отзывам админов/девопсов azure cloud — проприетарщина и отвратительное соотношение цена/качество, и не понятно кто его использует и зачем

                            Это второй пример дискредитации фактов, которые не укладываются в стройную теорию манифеста. Ну и не надо тогда претендовать на всеобъемлющесть, и пропихивать guidelines для частного случая везде, где только можно.
                              0
                              Хорошо, всё понятно. Вы — не можете.
                              Это _вы_ не можете, говорите за себя.

                              play.golang.org/p/yreNlk9bpoM
                              Вы думаете исключительно о том, _как_ логгировать в поток вывода
                              Не можете разработать код, который пишет в консоль, и полагаете, что другие не могут? Я пишу всё в консоль и в принципе не задумываюсь о реализации логгирования. Каким образом записи из консоли попадают в итоге в журнал — мне плевать, это решается в момент запуска приложения с помощью инфраструктурного ПО. Если по каким то причинам инфраструктурного ПО не хватает (чего не бывает практически никогда), то я или другой разработчик его создают, ни каких проблем с этим нет. Вы воюете с мельницей, поскольку такой подход к логгированию используется чуть более чем везде. Докер, systemd, облачные сервисы amazon, google cloud, heroku — всё это умеет работать с консольным выводом и полностью соответствует всем рекомендациям 12f, и мне не нужно ничего изобретать.
                              Вам приходится изобретать способ обернуть своё консольное приложение так, чтобы оно выглядело как Windows service
                              Я не пишу windows service. Я пишу бизнес фичи в соответствии с гайдлайнами, частью которых является консоль как единственный журнал. Потребуется запускать бизнес фичи в виде windows service — ни каких проблем, это бесплатно. Я в 90% случае даже представления не имею, на какой платформе будет запускаться моё приложение и в какой источник оно будет логгировать.
                              наплевав при этом на производительность, отказоустойчивость, простоту обслуживания.
                              Производительность и безопасность коня в вакууме — смешно. Попробуйте аргументировать.
                              Зона ответственности приложения заканчивается на вызове методов ILogger.Log*whatever*. Что происходит с логами дальше — это уже не забота разработчика.
                              Да, именно так у меня и есть. Благодаря следованию принципам 12f мои приложения не содержат плотформенных зависимостей, связанных с логгированием. А вот у вас если приложение использует event log windows для логгирования, то оно ни при каких обстоятельствах не будет работать на google cloude. И наоборот — если логгирует в google cloude, то не запустится под windows.
                              ссылка не имеет абсолютно никакого отношения к вопросу

                              ссылка про структурное логгирование не имеет ни какого отношения к структурному логгированию? ну ну
                              правильная реализация многопоточности — это уже непростой код, а шапкозакидательство не свидетельство квалификации. Обычно, наоборот.
                              Скорее свидетельсво узковатого кругозора у вас. Вы наверное имеете ввиду, что одновременно правильная и простая реализация многопоточности — это не про C#. На C# нельзя просто залочить мьютексом shared mutable state и одидать что это заработает. Ну так я с этим и не спорил. К счастью, я пишу на Go, и мне не сотавляет труда поставить мьютекс там, где есть доступ к стейту, и радоваться жизни.
                              второй пример дискредитации фактов, которые не укладываются в стройную теорию манифеста
                              Понятия не имею про azure, но и ничего не имею против неё — мне она фиолетова. Но вы опять не разобрались в вопросе. Azure умеет в докер, следовательно полностью соответсвует 12f в части логгирования. Скорее всего умеет и без докера (но кто в здравом уме будет деплоить C# без докера), просто вы не в курсе (а мне не интересно)
                                +1
                                Это _вы_ не можете, говорите за себя.

                                Приветствую усилия по написанию примера. Получилось, правда, заметно больше, чем 15-20 строк, никаких структурированных логов (да и вообще всё валится в одну кучу), и это мы его ещё не тестировали. При этом мы потеряли часть функционала Windows Service — просто, потому, что консольное приложение так не может, с безопасным завершением сервиса по команде остановки есть вопросы, куда пишутся логи самой обёртки тоже не очень понятно (ок, это может быть моё незнание golang, и они на самом деле тоже пишутся в eventLog, но я в этом не уверен). Это так, на вскидку. И всё это лишь для того, чтобы выводить логи в консоль. Не мучайтесь! Возьмите для C# Log4Net. Вы получите всё то же самое, только не нужно будет изголяться с обязательным пропусканием логов через стандартный вывод.

                                Не можете разработать код, который пишет в консоль, и полагаете, что другие не могут?

                                Как Вы там выше писали? — «Это _вы_ не можете, говорите за себя.» :) Перенаправить логи в стандартный вывод дело нехитрое, только бесмыссленное, при наличии более эффективных и надёжных способов.

                                Я пишу всё в консоль и в принципе не задумываюсь о реализации логгирования.

                                Это иллюзия, ибо заказчику нужно полное работающее решение, а не бинарник, пишуший в консоль. Поэтому вот то, что Вы написали в примере — будет частью Вашей работы, и Вам придётся этот код писать, тестировать, и поддерживать.

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

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

                                Да, именно так у меня и есть. Благодаря следованию принципам 12f мои приложения не содержат плотформенных зависимостей, связанных с логгированием.

                                Принцип 12f имеет к этому весьма опосредованное отношение, интерфейсы не его авторы изобрели. Скажем так, он предлагает делать это через выхлопную трубу.

                                На C# нельзя просто залочить мьютексом shared mutable state и одидать что это заработает.

                                Мьютекс на каждую запись в логи в продакшн сервисе? Это Вы серьёзно, да? Ну и про С# конечно тоже повеселило, спасибо.

                                Но вы опять не разобрались в вопросе. Azure умеет в докер, следовательно полностью соответсвует 12f в части логгирования.

                                Как Вы там писали? — А! «Это _вы_ не разобрались, говорите за себя.». Azure это делеко не только Docker, и, к счастью, не страдает параноидальным желанием засунуть всё в стандартный вывод, за что ему и спасибо.
                                  0
                                  Усилий не потребовалось, всё тривиально. Для безопасного завершения процесса потребуется доп. протокол через stdin дочернего процесса например, поскольку windows не умеет в сигналы unix. Если не уверены, можете самостоятельно проделать простейшую гимнастику (go mod init; go run. prog.exe; см. системный журнал «my service») и убедиться, как это сделал я. Или смотреть код библиотек, например, этой — там всего лишь вызовы windows api

                                  Структурное логгирование не имеет ни какого отношения к перенаправлению stdout. Оно реализуется библиотекой логгирования. В процессе разработки это просто вывод в консоль в формате logfmt — key=value, разделённые пробелами. В продакшене — json с добавлениями метки времени. Устанавливается в конфигурации запуска приложения.

                                  «Принцип 12f имеет к этому весьма опосредованное отношение, интерфейсы не его авторы изобрели.» — интерфейсы и 12F — параллельные понятия.

                                  «Мьютекс на каждую запись в логи в продакшн сервисе? Это Вы серьёзно, да? Ну и про С# конечно тоже повеселило, спасибо.» — ну а меня веселят ваши нервная реакция и синдром даннинга крюгера. В Go мьютексы бесплатны и не блокируют поток ос, поскольку все инструкции асинхронны. И да, защищать мьютексом в Go всё, что может использоваться конкурентно — это рекомендованные best practices
                                    0
                                    Если не уверены, можете самостоятельно проделать простейшую гимнастику


                                    Раз это windows service, то пример был скомпилирован и установлен как windows service.
                                    Баг №1 — интуиция меня не обманула. Нет, логи самой обёртки в event log не пишутся. Только обёрнутного приложения. Что интересно, если запускать с консоли, то да, логи обёртки тоже попадают в event log. Но мы же сервис тестируем.
                                    Баг №2 — если обёртка аварийно завершает работу, обёрнутое приложение продолжает работать, все логи уходят в никуда
                                    Баг №3 — после Бага №2, при попытке перезапустить сервис, запускается второй экземпляр оборачиваемого приложения.
                                    Баг №4 — если обёрнутое приложение завершается, обёртка-сервис продолжает работать. Сервиса уже нет, но никто ни сном, ни духом, и у Windows шанса перезапустить сервис или поднять предупреждение нет.
                                    Баг №5 — Если сообщение длинное, то оно в event log не пишется. Ошибок о сохранении сообщения в логе тоже не пишется. Длинные сообщения уходят в никуда.
                                    Баг №6 нам уже известен — при остановке сервиса обёрнутое приложение аварийно завершается, со всеми вытекающими.
                                    Про отсутствие хотя бы катерогий сообщений в обёртке я уже молчу.

                                    Эти ошибки я нашёл за 10 минут экспериментов, и, они отнюдь не мелочи, они могут обернуться финансовыми и репутационными потерями для заказчика.

                                    В Go мьютексы бесплатны и не блокируют поток ос, поскольку все инструкции асинхронны.

                                    Какими бы бесплатными не были мьютексы, какими бы асинхронными не были инструкции, тот код, что защищен мьютексом, всегда выполняется только в одном экземпляре, последовательно. А под мьютексом у Вас — операция ввода-вывода, недешевая, надо сказать, операция. Очевидно, что за одну единицу времени больше запросов, чем количество раз, которое может исполниться код под мьютексом, сервис исполнить не сможет. А учитывая, что чаще всего на один запрос пишется несколько сообщений, можно смело уменьшать число запросов ещё на порядок. И решение это не масштабируется. Это обычное бутылочное горлышко — ошибка новичка.

                                    Ну и про парсить stdin в веб-сервисе — это уже полная бесмыслица и мартышкин труд.
                                      0
                                      «логи самой обёртки в event log не пишутся» — я и не собирался. зачем мне это делать? задача стояла перенаправить лог дочернего процесса а не основного

                                      2 — да, я забыл вставить инструкцию, закрывающую запущенный сервис при выходе из main

                                      3,4,6 — неуместные придирки. Естественно для демонстрационного примера обработка ошибок и перезапуск сервиса меня не интересуют

                                      5 — ничего страшного и тоже в данном случае не важно.

                                      Да, для продакшена я бы написал интеграционные тесты, проверяющие запуск, перезапуск, остановку, запись в сист. журнал и проч. — и что?

                                      По поводу того, что запись логов в конечный журнал может тормозить — естественно может, спасибо кэп. По этому маршрутизаторы журналов, такие как promtail, Logplex, Fluent и logstash, кешируют и буферизирут свой ввод. И на приложении, из которого маршрутизатор получает ввод, эти тормоза -внезапно! — ни коим образом не отражаются. Ошибка новичка в данном случае — это святая уверенность в том, что если новичок не может что-то сделать быстро и просто, то это в принципе не было реализовано до него

                                      «Ну и про парсить stdin в веб-сервисе — это уже полная бесмыслица и мартышкин труд.»

                                      а само по себе использование служб виндовз в качестве веб сервисов — бесСмыслица (с двумя «с») и мартышкин труд.
                                        0
                                        «логи самой обёртки в event log не пишутся» — я и не собирался. зачем мне это делать?

                                        А куда они тогда пишутся? Ведь обёртка эти сообщения создаёт. Значит, замысел их выводить тоже был. Или они там для красоты? :)
                                        А вот эта реплика — "Если не уверены, можете самостоятельно проделать простейшую гимнастику (go mod init; go run. prog.exe; см. системный журнал «my service») и убедиться, как это сделал я" очевидно, относится вот к этому моему комментарию: куда пишутся логи самой обёртки тоже не очень понятно (ок, это может быть моё незнание golang, и они на самом деле тоже пишутся в eventLog, но я в этом не уверен)
                                        Всё это обычное переобувание на лету.

                                        неуместные придирки. Естественно для демонстрационного примера обработка ошибок и перезапуск сервиса меня не интересуют


                                        Я просто напомню, с чего возник интерес к примеру. Конкретно, вот с этого Вашего утверждения: Когда мне нужно запускать веб-сервис в виде службы windows, я напишу обычный веб-сервис и без проблем запущу его в виде службы и направлю stdout куда мне надо без посторонних «агентов» и докеров. Цена вопроса — ~15-20 строк банального кода. . На проверку оказалось, что чудес на свете не бывает, что написать обёртку займёт отнюдь не 15-20 строк кода, а на пару порядков больше, да и времени на отладку уйдёт заметное количество. Приврали ради красного словца. Ну что же, бывает.

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

                                        Ну что же опускаться до подмены сказанного собеседником на то, что удобно для себя? Напомню, речь не идёт о том, как _собираются_ логи с потока. А о том, как _сам сервис_ пишет их в поток вывода. Ваше предложение: достаточно простой обёртки над выводом в stdut… К счастью, я пишу на Go, и мне не сотавляет труда поставить мьютекс там, где есть доступ к стейту, и радоваться жизни. Ваше предложение однозначно понимается как устроить бутылочное горлышко, посадив запись в поток вывода под мьютекс. Именно в Вашей обёртке и будут тормоза.

                                        а само по себе использование служб виндовз в качестве веб сервисов — бесСмыслица и мартышкин труд.

                                        Заметьте, не я это предложил: Когда мне нужно запускать веб-сервис в виде службы windows, я напишу обычный веб-сервис и без проблем запущу его в виде службы и направлю stdout куда мне надо без посторонних «агентов» и докеров.
                                        Если вынусть слово«веб» из этого тезиса, опять же, ничего не изменится. Парсить stdin в windows service, вместо того, чтобы принять callback от Service Control Manager — это бессмыслица, и мартышкин труд. Как и писать логи из windows service в поток вывода, потом городить обёртку, которая их будет перекладывать в event log.

                                        бесСмыслица (с двумя «с»)

                                        Вы хотите поиграть в игру про правописание? Это было бы несложно:

                                        Ни какие, ни какая — пишется слитно.
                                        Не уместны — тоже слитно.
                                        Ни кто — снова, слитно.
                                        логии — пишется с одной «и»

                                        и т.д. Но здесь, всё же, не занятия по правописанию, и, придираться к опечаткам в комментариях на Хабре — это несусветная глупость.
                                          0
                                          «А куда они тогда пишутся? Ведь обёртка эти сообщения создаёт. Значит, замысел их выводить тоже был» — они пишутся в консоль и более никуда. Это нормально для консольного приложения системного и инфраструктурного назначения. Почему это вас так напрягает? Я не ставил задачи перенаправить это в системный журнал

                                          «15-20 строк кода, а на пару порядков больше, да и времени на отладку уйдёт заметное количество» — естественно для рабочего приложения времени уйдёт больше, чем на демонстрационный пример. Я вам показал что это в принципе просто решается, в контексте обсуждения этого достаточно.

                                          «речь не идёт о том, как _собираются_ логи с потока. А о том, как _сам сервис_ пишет их в поток вывода» — ровно стой же скоростью, с которой строка выводится в консоль при разработке, не больше и не меньше.
                                            0
                                            они пишутся в консоль и более никуда. Это нормально для консольного приложения системного и инфраструктурного назначения. Почему это вас так напрягает? Я не ставил задачи перенаправить это в системный журнал


                                            Да потому, что обёртка — это не консольное приложение, а windows service. А у windows services нет консоли :) Вы помните вообще, зачем Вы взялись её писать?
                                            Хорошо, допустим, что не было замысла писать сообщения обёртки в event log. Тогда почему в режиме консольного приложения она как раз это и делает? Этот пример вообще Вы писали?

                                            естественно для рабочего приложения времени уйдёт больше, чем на демонстрационный пример. Я вам показал что это в принципе просто решается, в контексте обсуждения этого достаточно.


                                            Снова переобуваемся на лету. Вы сказали дословно: «Когда мне нужно запускать веб-сервис в виде службы windows, я напишу обычный веб-сервис и без проблем запущу его в виде службы и направлю stdout куда мне надо без посторонних «агентов» и докеров. Цена вопроса — ~15-20 строк банального кода.». Речь идёт здесь не о примере, а именно о продукте. Вопрос про пример появился позже.

                                            ровно стой же скоростью, с которой строка выводится в консоль при разработке, не больше и не меньше.

                                            Разумеется! И это ни разу не быстро. И сидит под mutex, поэтому не масштабируется вширь вообще никак. Пока один поток пишет — все остальные ждут. Это как очередь на кассу с одним кассиром в магазине. Хоть миллион человек туда запусти — если кассир выпускает одного человека в минуту, с покупками оттуда за час выйдет ровно 60 человек.
                                              0
                                              И сидит под mutex, поэтому не масштабируется вширь вообще никак. Пока один поток пишет — все остальные ждут.

                                              Дико извиняюсь, но в итоге все равно данные в логи пишутся последовательно, так что где-нибудь эта очередь да возникнет. Если приложение генерирует такое количество логов, что они не влазят в stdout то надо уже разобраться, нужно ли такое количество информации именно в журналах, может надо для нее какое-то более подходящее хранилище использовать. Представляете себе миллион сообщений в час в event log'е?
                                                +1
                                                За что Вы извиняетесь-то? :)
                                                Вы, конечно, правы, не надо писать в windows event log миллионы сообщений, он для этого не предназначен. Для этого есть другие системы накопления логов. И разобираться с тем, что пишется в логи тоже нужно. Однако, нагрузка может быть вполне обоснованной. А потеря производительности при последовательной записи в stdout выражается не бинарным стостоянием (либо есть, либо нет), она варьируется в зависимости от нагрузки на веб сервис, и заметные потери могут проявиться уже при частичной загрузке потока. На мой взгляд, просто не надо к нему привязываться, и, не надо использовать такую вот обёртку вокруг него, которая выполняет операции ввода-вывода под мьютексом. Есть готовые библиотеки, в которых поборолись за масштабируемость и производительность. И прямые каналы записи в системы накопления логов быстрее, и, можно писать параллельно с нескольких потоков, и, не нужно сериализовать/восстанавливать данные лишний раз. Всё это можно сделать правильно, не привязываясь к конкретной среде исполнения.
                      0
                      По умолчанию stdout для разработчика — это консоль практически всегда.


                      В браузере, например, есть консоль, но нет stdout.
            0
            <промахнулся с веткой>

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

            Самое читаемое