Задать вопрос
  • Почему GORM создает пустую таблицу?

    Сделайте типы object и reference экспортируемыми. Иначе у Gorm будут проблемы с рефлексией, и он не увидит поля.
    Ответ написан
    Комментировать
  • Как сделать правильную табуляцию в html?

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

    Исходя из этого можно заключить, что любые style guides, которых придерживаются программисты, - это просто соглашение, действующее в рамках какой-то одной организации либо сообщества. Вы устраиваетесь на работу и там вам дадут документ либо конфигурационный файл, в котором будут перечислены все правила, которых придерживаются в данной организации: 2, 4, 8 пробелов, символ табуляции, полное отсутствие пробелов между тегами. И вот этих правил надо придерживаться.

    Таким образом, в вашей ситуации случилось одно из двух:
    • преподаватель изначально дал вам список всех правил, но вы просто забыли об этом либо пропустили это занятие. Тогда преподаватель прав, и вам надо уточнить у него весь этот список правил и сказать, что больше так не будете.
    • преподаватель не дал вам этот список, поэтому, если у вас во всём проекте одинаковое количество пробелов в отступах в HTML, то правы вы, и вы можете потребовать от преподавателя предоставить вам список правил, которые он требует, и вежливо пояснить ему, что он не прав, критикуя вас за выбор количества пробелов без предварительного предоставления подробных style guides.
    Ответ написан
  • Что чаще всего пишут на Go?

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

    Основной домен языка - это Web. Это и полноценные REST (и не только) API, это и отдельные узлы систем. А я, например, стал писать на Go и фронтенд, познакомившись с замечательной JavaScript библиотекой HTMX.

    Многие люди пишут на Go консольные приложения. Я часто всякие утилитки пишу вместо того, чтобы мучиться с корявым синтаксисом bash, от которого меня тошнит.

    Кстати, Докер и Кубернетес написаны на Go, но это, скорей исключение.

    Есть даже пакеты, позволяющие писать десктопные приложения.

    На Go отлично писать всяких ботов.
    Ответ написан
    2 комментария
  • Почему не запускается PhpStorm?

    Самый очевидный способ:
    - удалите PhpStorm
    - удалите все версии Java
    - установите PhpStorm
    Ответ написан
    3 комментария
  • Как из строки с путём получить элемент массива?

    <?php
    function getArrayValueByPath($array, $path, $separator = "/")
    {
        // Разбиваем путь на ключи
        $keys = explode($separator, $path);
        // Текущий уровень массива - начинаем с корня
        $current = $array;
    
        foreach ($keys as $key) {
            // Если ключа нет в массиве, то выбрасываем исключение
            if (!isset($current[$key])) {
                throw new \Exception(
                    "Ключ $key не найден в массиве по адресу '$path'"
                );
            }
            // Переходим к следующему уровню вложенности массива
            $current = $current[$key];
        }
    
        // Возвращаем значение по заданному пути
        return $current;
    }
    
    // Использование функции:
    
    $arr = [
        "k1" => [
            "k2" => [
                "k3" => [
                    "k4" => "v",
                ],
            ],
        ],
    ];
    
    $result = getArrayValueByPath($arr, "k1/k2/k3");
    
    var_dump($result);
    
    /** Результат выполнения:
    
    array(1) {
      ["k4"]=>
      string(1) "v"
    }
    
    */
    Ответ написан
    Комментировать
  • Как научиться декомпозиции в ООП?

    Понятие "декомпозиция" бывает слишком абстрактным для понимания начинающими.
    Поэтому, я порекомендовал бы изучить принципы SOLID. Вся их сущность как раз и направлена на понимание декомпозиции.

    Есть куча книжек, роликов на ютубчике и т.д.
    Но можно начать как раз с ChatGPT.
    Задайте ему вот такую задачку:

    Объясни принципы SOLID. Используй примеры на PHP. Объясняй как десятилетнему ребёнку


    Не стесняйтесь упоминания ребенка в сложных вопросах. Он тогда очень красочно и очень понятно описывает. Такое лучше входит в мозг.
    Ответ написан
    Комментировать
  • Как реализовать вертикальные линии между блоками при адаптивной вёрстке?

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

    Но... Есть нюанс... Columns - это колонки. Т.е. статьи будут заполняться колонками, а не строками, как в дефолтных grid или flex

    Ответ написан
    2 комментария
  • Почему ошибка в SendMessageParams?

    А почему бы вам не установить курсор на "SendMessageParams" и не нажать правой кнопкой мыши на этом слове, да и не перейти к объявлению типа, чтобы просто посмотреть, а какие же там поля требуются в этой структуре?
    И, странное дело, нет такого поля ReplyToMessageID у типа SendMessageParams, о чём вам прямо и говорит компилятор.

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

    // SendMessageParams - Represents parameters of sendMessage method.
    type SendMessageParams struct {
    	// BusinessConnectionID - Optional. Unique identifier of the business connection on behalf of which the
    	// message will be sent
    	BusinessConnectionID string `json:"business_connection_id,omitempty"`
    
    	// ChatID - Unique identifier for the target chat or username of the target channel (in the format
    	// @channel_username)
    	ChatID ChatID `json:"chat_id"`
    
    	// MessageThreadID - Optional. Unique identifier for the target message thread (topic) of the forum; for
    	// forum supergroups only
    	MessageThreadID int `json:"message_thread_id,omitempty"`
    
    	// Text - Text of the message to be sent, 1-4096 characters after entities parsing
    	Text string `json:"text"`
    
    	// ParseMode - Optional. Mode for parsing entities in the message text. See formatting options
    	// (https://core.telegram.org/bots/api#formatting-options) for more details.
    	ParseMode string `json:"parse_mode,omitempty"`
    
    	// Entities - Optional. A JSON-serialized list of special entities that appear in message text, which can be
    	// specified instead of parse_mode
    	Entities []MessageEntity `json:"entities,omitempty"`
    
    	// LinkPreviewOptions - Optional. Link preview generation options for the message
    	LinkPreviewOptions *LinkPreviewOptions `json:"link_preview_options,omitempty"`
    
    	// DisableNotification - Optional. Sends the message silently
    	// (https://telegram.org/blog/channels-2-0#silent-messages). Users will receive a notification with no sound.
    	DisableNotification bool `json:"disable_notification,omitempty"`
    
    	// ProtectContent - Optional. Protects the contents of the sent message from forwarding and saving
    	ProtectContent bool `json:"protect_content,omitempty"`
    
    	// MessageEffectID - Optional. Unique identifier of the message effect to be added to the message; for
    	// private chats only
    	MessageEffectID string `json:"message_effect_id,omitempty"`
    
    	// ReplyParameters - Optional. Description of the message to reply to
    	ReplyParameters *ReplyParameters `json:"reply_parameters,omitempty"`
    
    	// ReplyMarkup - Optional. Additional interface options. A JSON-serialized object for an inline keyboard
    	// (https://core.telegram.org/bots/features#inline-keyboards), custom reply keyboard
    	// (https://core.telegram.org/bots/features#keyboards), instructions to remove a reply keyboard or to force a
    	// reply from the user
    	ReplyMarkup ReplyMarkup `json:"reply_markup,omitempty"`
    }


    А если точно так же перейти к определению типа поля ReplyParameters, то можно увидеть как раз то самое искомое вами поле MessageID

    // ReplyParameters - Describes reply parameters for the message that is being sent.
    type ReplyParameters struct {
    	// MessageID - Identifier of the message that will be replied to in the current chat, or in the chat chat_id
    	// if it is specified
    	MessageID int `json:"message_id"`
    
    	// ChatID - Optional. If the message to be replied to is from a different chat, unique identifier for the
    	// chat or username of the channel (in the format @channel_username). Not supported for messages sent on behalf
    	// of a business account.
    	ChatID ChatID `json:"chat_id,omitempty"`
    
    	// AllowSendingWithoutReply - Optional. Pass True if the message should be sent even if the specified
    	// message to be replied to is not found. Always False for replies in another chat or forum topic. Always True
    	// for messages sent on behalf of a business account.
    	AllowSendingWithoutReply bool `json:"allow_sending_without_reply,omitempty"`
    
    	// Quote - Optional. Quoted part of the message to be replied to; 0-1024 characters after entities parsing.
    	// The quote must be an exact substring of the message to be replied to, including bold, italic, underline,
    	// strikethrough, spoiler, and custom_emoji entities. The message will fail to send if the quote isn't found in
    	// the original message.
    	Quote string `json:"quote,omitempty"`
    
    	// QuoteParseMode - Optional. Mode for parsing entities in the quote. See formatting options
    	// (https://core.telegram.org/bots/api#formatting-options) for more details.
    	QuoteParseMode string `json:"quote_parse_mode,omitempty"`
    
    	// QuoteEntities - Optional. A JSON-serialized list of special entities that appear in the quote. It can be
    	// specified instead of quote_parse_mode.
    	QuoteEntities []MessageEntity `json:"quote_entities,omitempty"`
    
    	// QuotePosition - Optional. Position of the quote in the original message in UTF-16 code units
    	QuotePosition int `json:"quote_position,omitempty"`
    }


    Итого, в вашем случае будет как-то так:

    id, ticket, reply_message := db.GetTicketAndMessage(message.ReplyToMessage.MessageID, user.ID)
    	if ticket == nil {
    		_, _ = bot.SendMessage(&telego.SendMessageParams{
    			ChatID:           telego.ChatID{ID: message.Chat.ID},
    			Text:             "This ticket or message does not exist.",
    			ReplyParameters:  &telego.ReplyParameters{
                                    MessageID: message.MessageID
                            },
    			ParseMode:        "HTML",
    		})
    		return
    	}
    Ответ написан
    4 комментария
  • Как на MacOS (браузер Chrome) проинспектировать поповер, который исчезает если убрать мышку?

    Находите элемент, на котором срабатывает :hover, и потом просто устанавливаете ему насильно псевдокласс :hover.
    Таким образом он и без мышки будет находиться в этом состоянии.
    Соответственно, при этом появится и сам попап, и вы сможете спокойно его проинспектировать.

    677d33dac2b98296274838.png

    В качестве альтернативы можно использовать следующий подход:

    1. Открываете инструменты разработчика не прикреплёнными к браузеру, а в отдельном окне. Заранее открываете там консоль.

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

    3. Не двигая указатель мыши, переключаетесь с помощью клавиатуры на окно с инструментами разработчика, где заранее открыта консоль. Вводите там команду "debugger". Основное окно браузера фризится вместе с видимым попапом.

    4. Спокойно инспектируете попап.
    Ответ написан
    7 комментариев
  • Как сделать край блока волной?

    Использовать свойство CSS clip-path

    Гуглить, как можно сделать волну при помощи clip-path. Пример запроса: "clip-path wave"

    Есть вот такой генератор, например. https://www.wavegenerator.app/
    Ответ написан
  • Какие есть высокоуровневые компонентные javascript фреймворки?

    Если я правильно понял вашу фразу "связано (полу-)автоматически с бэкендом", то вы имеете ввиду SSR (Server Side Rendering)

    Если бы я не увидел у вас ссылку на Quarkus (то бишь Java), я бы посоветовал вам не терять время на изучение чего-то странного и непопулярного, и приступить непосредственно к изучению React и его фреймворка для SSR Next.js. Я не люблю ни то, ни другое, но это, к сожалению, индустриальный стандарт, и очень желательно их знать.

    Но так как я увидел у вас бэкенд на Java и желание всё же иметь ту самую "полу-автоматическую связь с бэкендом", то можете поступить радикальным образом: взять какую-то UI библиотеку из веб-компонентов (например Shoelace), или более традиционные библиотеки UI, основанные на tailwindCSS (например, daisyUI).
    Весь HTML рисовать на бэкенде, а оживлять это всё с помощью библиотеки HTMX. Это позволит вам всю логику и стейт держать на бэкенде, и, в то же время, иметь очень хорошую интерактивность. https://www.youtube.com/watch?v=vgAKrggD0vQ&t=173s
    Ответ написан
  • Как организовать отправку сообщений в множество микросервисных нод?

    Берете какой-нибудь брокер сообщений.
    Например, Rabbitmq.
    Там создаёте exchange с типом fanout. Туда будете слать сообщения из сервиса-отправителя.
    Каждый сервис-получатель будет подключаться к этому exchange и для каждого будет создаваться своя очередь, куда будут попадать все сообщения. И каждый сервис спокойно сам вычитает свою очередь. И даже если он упадёт, то дочитает их после того, как подымется.
    https://youtu.be/P_EtQ-s-hiU?si=tavSDSzTnGpqQrAY
    Ответ написан
    2 комментария
  • Как получить группы, в которых состоит пользователь?

    Вот ответ для вашего конкретного случая в Windows.

    package main
    
    import (
    	"fmt"
    	"os/exec"
    	"strings"
    )
    
    func getGroups() ([]string, error) {
    	var groups []string
    
    	patch := "/groups"
    	cmd := exec.Command("whoami", patch)
    	output, err := cmd.Output()
    	if err != nil {
    		return nil, err
    	}
    
    	lines := strings.Split(string(output), "\n")
    	for _, line := range lines {
    		if strings.HasPrefix(line, "S-1-5-21") {
    			groups = append(groups, line)
    		}
    	}
    
    	return groups, nil
    }
    
    func main() {
    	groups, err := getGroups()
    	if err != nil {
    		fmt.Printf("Ошибка получения групп пользователя: %v\n", err)
    		return
    	}
    
    	fmt.Println("Группы пользователя:", groups)
    }
    Ответ написан
    21 комментарий
  • Чем отличается функция от конструктора и где применять то или это?

    К большому моему (личному) сожалению, Javascript - это язык, в котором возможно и разрешено вообще всё. Язык постоянно дополняется из "хотелок" пользователей.

    И вот в один момент разработчики спецификации ECMA-script решили, а давайте мы всё же оправдаем первые 4 буквы в названии языка, а именно "Java", и дадим пользователям сахарок, нарисованный над нашим прототипным наследованием, чтобы они могли везде писать class, extends и implements, как и все остальные ООП-динозавры. И сделали это.

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

    class ClassWithPrivate {
      #privateField;
      publicField;
    
      constructor() {
        this.#privateField = "Доступ только изнутри класса";
      }
    }
    
    const instance = new ClassWithPrivate();
    
    instance.publicField = "Доступ извне класса";
    instance.#privateField; // Ошибка: SyntaxError: Private field '#privateField' must be declared in an enclosing class


    Еще одной особенностью конструктора является то, что он активно используется при наследовании (крестится и плюётся через левое плечо от отвращения). Если в дочернем классе не описан свой конструктор, то будет использован конструктор родителя. Это упрощает ООП.

    class Animal {
    
      constructor(name) {
        this.speed = 0;
        this.name = name;
      }
    
      run(speed) {
        this.speed = speed;
        alert(`${this.name} бежит со скоростью ${this.speed}.`);
      }
    
      stop() {
        this.speed = 0;
        alert(`${this.name} стоит.`);
      }
    
    }
    
    class Rabbit extends Animal {
      hide() {
        alert(`${this.name} прячется!`);
      }
    
      stop() {
        super.stop(); // вызываем родительский метод stop
        this.hide(); // и затем hide
      }
    }
    
    let rabbit = new Rabbit("Белый кролик"); // используется конструктор родителя
    
    rabbit.run(5); // Белый кролик бежит со скоростью 5.
    rabbit.stop(); // Белый кролик стоит. Белый кролик прячется!


    Таким образом, если вдарились в классический ООП, пользуйтесь везде конструктором, потому что ООП развивается десятилетиями, люди прошли сквозь страдания и выработали определённые "лучшие практики" для ООП.
    Если же вы используете объекты в основном как очень простые структуры без классической инкапсуляции с приватными полями, не используете наследование, а весь ваш код написан, в основном, на функциях, то делайте, ради бога, что вам вздумается, и отдельная функция-фабрика для создания объектов - это тоже вариант.
    Ответ написан
    Комментировать
  • Как сделать так, чтобы при нажатии не исчезало?

    Метод называется checkbox hack.
    Мы на самом деле используем checkbox и его превосходное свойство работать даже при нажатии на метку, которая с ним ассоциирована. Таким образом мы скрываем сам нативный checkbox, а его метку стилизуем так, как нам надо, хоть в виде кнопки, хоть в виде чего угодно. И вместо :active у кнопки проверяем :checked у чекбокса
    https://css-tricks.com/the-checkbox-hack/

    А вот ещё набор идей
    https://codepen.io/
    В поиске наберите "toggle-switch"

    И вот что-то похожее на ваш случай
    Ответ написан
    7 комментариев
  • Как заставить предупреждение срабатывать только после нажатия "Да/Нет" в диалоговом окне?

    Первое, что вам надо сделать - изучить самые базовые основы Javascript и DOM, и как это всё работает.
    Вы должны понимать, что Javascript - это не PHP. PHP работает только в один поток и выполняет операции исключительно одну за другой по порядку. В Javascript же есть возможность выполнять код асинхронно с использованием такого инструмента, как Event Loop. Ещё обязательно изучить, что такое Promise и как подсластить его использование при помощи сахарка async - await

    Т.е. ваш обычный код, который выполняется по порядку, может выполняться как бы "одновременно" с кодом, который выполняется при нажатии на кнопку. Т.е. пока ваша кнопка ждёт события click, чтобы выполнить какой-то код по нажатию, остальная программа спокойно себе исполняется по порядку. Таким образом, пользователь еще не успел кликнуть, но ваша программа уже создала диалог при помощи функции Myconfirmselect, и спокойно себе приступила к следующей операции. А этой следующей операцией как раз и является тот самый alert("!"). Вот он и появляется на экране.

    Т.е. нам надо как бы подсунуть этот alert внутрь функции Myconfirmselect. Как это сделать? В Javascript функции - это такие же объекты. Т.е. функцию можно назначить какой-то переменной и передать эту переменную в другую функцию. Вот мы и передаём наш callback в функцию Myconfirmselect просто в качестве аргумента, и вызываем этот аргумент как функцию уже в том месте, где обрабатывается событие click на кнопку.

    P.S. В коде постоянно происходит выборка одного и того же элемента. Это не очень хорошо, потому что такая выборка не бесплатна, все операции с DOM - довольно медленные, поэтому я позволил себе переписать часть кода, не меняя самой его структуры, чтобы вам было понятнее. Выбрав один раз наш конкретный dialog нет смысла выбирать его постоянно еще и еще раз. Запихнём его в переменную и будем с ним работать. То же самое касается и textarea и div

    <html>
        <body onload="Load()">
            <dialog></dialog>
            <script>
                function Load() {
                    setTimeout(() => {
                        // Создаём колбэк, функцию, которая будет вызываться только после нажатия на кнопку
                        const callback = function () {
                            alert("!");
                        };
    
                        Myconfirmselect(
                            "Готовы?" +
                                "¤" +
                                "Да" +
                                "¥" +
                                "alert('Да');" +
                                "º" +
                                "Нет" +
                                "¥" +
                                "alert('Нет');",
                            callback, // Передаём колбэк в функцию создания диалога
                        );
                    }, 500);
                }
    
                function Myconfirmselect(VarInput, callback) {
                    const dialog = document.querySelector("dialog");
                    dialog.innerHTML += `
       <div>
        <textarea></textarea>
       </div>
       `;
    
                    const textarea = dialog.querySelector("textarea");
                    const div = dialog.querySelector("div");
                    dialog.style.textAlign = "center";
                    textarea.style.width = "100%";
                    textarea.style.textAlign = "center";
                    textarea.style.resize = "none";
                    textarea.readOnly = true;
                    textarea.value = VarInput.split("¤")[0];
                    for (
                        let i = 0;
                        i < VarInput.split("¤")[1].split("º").length;
                        i++
                    ) {
                        const VarButton = document.createElement("button");
                        VarButton.textContent = VarInput.split("¤")[1]
                            .split("º")
                            [i].split("¥")[0];
                        VarButton.id = VarInput.split("¤")[1]
                            .split("º")
                            [i].split("¥")[1];
                        VarButton.style.marginLeft = "10px";
                        VarButton.style.marginTop = "10px";
                        VarButton.addEventListener("click", () => {
                            eval(event.target.id);
                            // Вызов того самого callback после нажатия на кнопку
                            callback();
                            //ButtonClosealert();
                        });
                        div.appendChild(VarButton);
                    }
                    function ButtonClosealert() {
                        dialog.style.animation = "AnimFormHide 0.1s both";
                        setTimeout(() => {
                            dialog.style.textAlign = "initial";
                            const div = dialog.querySelector("div");
                            dialog.removeChild(div);
                            dialog.close();
                        }, 100);
                    }
                    dialog.style.animation = "AnimFormShow 0.25s both";
                    dialog.showModal();
                }
            </script>
            <style>
                @keyframes AnimFormShow {
                    0% {
                        transform: translateY(-150%);
                    }
                }
    
                @keyframes AnimFormHide {
                    100% {
                        transform: translateY(-150%);
                        display: none;
                    }
                }
    
                dialog,
                #DialogCycle {
                    border: 2px solid black;
                    border-radius: 15px;
                }
    
                dialog div,
                #DialogCycle div {
                    position: initial;
                }
    
                dialog::backdrop,
                #DialogCycle::backdrop {
                    background: rgba(0, 0, 0, 0.6);
                }
            </style>
        </body>
    </html>
    Ответ написан
    Комментировать
  • Какие минусы у данного решения?

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

    https://silktide.com/toolbar/screen-reader-simulator/

    https://chromewebstore.google.com/detail/web-disab...

    Ваша цель:
    - сделать так, чтобы устройства для слепых "screen reader" адекватно сообщали пользователю, что от него требуется.
    - чтобы контраст был адекватным, чтобы и слабовидящие могли пользоваться
    Ответ написан
    2 комментария
  • Slog. Нужно ли предавать логгер через параметры или объявить его на уровне модуля и обращаться к нему?

    Насчёт параллельных обращений. slog очень гибкий и продуманный логгер. Не стоит беспокоиться насчёт параллельных обращений, там всё отлично с этим.

    Насчёт того, использовать ли логгер глобально...
    На этот вопрос трудно ответить, не зная, какой у вас проект.

    Лично я предпочитаю пользоваться принципом "Бритвы Оккама" и не вводить новых сущностей без необходимости.

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

    Тем более, что даже для тестов вы можете написать для slog какой-то особый хэндлер, который будет работать так, как надо в тестах.

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

    Вот вам крайне увлекательное и крайне полезное видео, как из глобального slog можно сделать просто монстра логгирования на все случаи жизни.
    https://www.youtube.com/watch?v=p9XiOOU52Qw

    Вот пример того, как я настраивал логгер в одном из маленьких проектов. Он определяет по конфигу, с какого уровня надо логгировать, в каком формате, и в какой поток (stdout, stderr, файл)

    package app
    
    import (
    	"fmt"
    	"io"
    	"log/slog"
    	"os"
    )
    
    // SetupLogger создаёт логгер на основании конфига
    // Конфигурируем дефолтный slog и возвращаем функцию закрытия файла логов, 
    // если был выбран файл как место логгирования.
    func SetupLogger(cfg config.Config) func() error {
    	closer := func() error { return nil }
    
    	level := cfg.LogLevel
    
    	var w io.Writer
    
    	switch cfg.LogOutput {
    	case config.LogOutputStderr:
    		w = os.Stderr
    	case config.LogOutputStdout:
    		w = os.Stdout
    	case config.LogOutputFile:
    		w, closer = getFileWriter(cfg)
    	}
    
    	var handler slog.Handler
    	switch cfg.LogFormat {
    	case config.LogFormatText:
    		handler = slog.Handler(slog.NewTextHandler(w, &slog.HandlerOptions{Level: level}))
    	case config.LogFormatJSON:
    		handler = slog.Handler(slog.NewJSONHandler(w, &slog.HandlerOptions{Level: level}))
    	}
    
    	slog.SetDefault(slog.New(handler))
    
    	return closer
    }
    
    // getFileWriter возвращает файл, куда писать логи и функцию закрытия этого файла
    func getFileWriter(cfg config.Config) (*os.File, func() error) {
    	logDir := fmt.Sprintf("%s/%s", cfg.VarDir, cfg.LogDir)
    	err := os.MkdirAll(logDir, 0755)
    	if err != nil {
    		panic(fmt.Sprintf("error creating directory: %s;  %v", logDir, err))
    	}
    
    	filename := fmt.Sprintf("%s/%s.log", logDir, cfg.AppEnv)
    	f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
    	if err != nil {
    		panic(fmt.Sprintf("error opening file: %s %v", filename, err))
    	}
    	return f, f.Close
    }


    package main
    
    import "app"
    
    func main() {
    
            // .....
    
    	closeLogFile := app.SetupLogger(cfg)
    	defer closeLogFile()
    
            // ...
    }
    Ответ написан
    5 комментариев
  • Как в js, scss, html реализовать tooltip который показывается если навести на объект ниже?

    А зачем такие сложности с такой простой задачей, как тултипы? Их ведь можно реализовать полностью на CSS, просто добавив нужному элементу, например, атрибут "tooltip"



    Анимацию уже сами добавите, если очень нужна

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



    .custom-tooltip:has(+ *:hover) означает, что выбрать надо те элементы .custom-tooltip, у которых на следующем за ними элементе наведена мышь.
    "+" выбор непосредственно следующего элемента
    "+ *"- выбор любого непосредственного следующего элемента
    "+ *:hover" - выбор любого непосредственного следующего элемента, на который наведена мышь.
    has  выбирает элементы .custom-tooltip, с которым связаны элементы, выбранные при помощи уточняющего селектора в скобках.
    Ответ написан
    1 комментарий
  • Как избежать race condition при вставки новой записи в бд SQL, PHP?

    Создайте в таблице какой-то уникальный индекс, основанный на ваших данных, и второй раз одни и те же данные просто не получится вставить.
    Ответ написан
    2 комментария