Форум программистов, компьютерный форум, киберфорум
C#: Web, ASP.NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
.NET 8

Serilog до старта приложения

08.02.2025, 16:17. Показов 1547. Ответов 18

Студворк — интернет-сервис помощи студентам
Пытаюсь запуститься по документации от Serilog, чтобы собрать логи на момент создания WebApplication.

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

В поиске часто нахожу .UseSerilog(), но в документации сейчас .AddSerilog().

Кто сталкивался с такой настройкой, подскажите плиз, что не так делаю?

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
using Serilog;
using Serilog.Core;
 
var levelSwitch = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Verbose);
var levelSwitch2 = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Verbose);
var levelSwitch3 = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Verbose);
 
Log.Logger = new LoggerConfiguration()
 
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.SQLite("Logs.db3",
        levelSwitch: levelSwitch,
        maxDatabaseSize: 100)
 
    .MinimumLevel.ControlledBy(levelSwitch2)
    .WriteTo.SQLite("Logs2.db3",
        levelSwitch: levelSwitch2,
        maxDatabaseSize: 100)
#if DEBUG
    .WriteTo.Console(levelSwitch: levelSwitch)
#endif
    .CreateBootstrapLogger();
 
 
try
{
    var builder = WebApplication.CreateBuilder(args);
 
    builder.Services.AddSingleton(levelSwitch);
    builder.Services.AddSingleton(levelSwitch2);
    builder.Services.AddSingleton(levelSwitch3);
 
    builder.Services.AddSerilog((services, lc) => lc
        .ReadFrom.Configuration(builder.Configuration)
        .ReadFrom.Services(services)
        .Enrich.FromLogContext()
 
        .MinimumLevel.ControlledBy(levelSwitch3)
        .WriteTo.SQLite("Logs3.db3",
            levelSwitch: levelSwitch3,
            maxDatabaseSize: 100)
        );
 
 
    Log.Logger.Verbose("Verbose");
    Log.Logger.Debug("Debug");
    Log.Logger.Information("Information");
    Log.Logger.Warning("Warning");
    Log.Logger.Error("Error");
    Log.Logger.Fatal("Fatal");
 
 
    var app = builder.Build();
 
    app.Run();
}
catch (Exception ex)
{
    Log.Logger.Fatal(ex, "Приложение завершилось неуспешно.");
}
finally
{
    Log.Logger.Fatal("Успешная остановка приложения.");
    Log.CloseAndFlush();
}
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
08.02.2025, 16:17
Ответы с готовыми решениями:

Serilog как работать не со статическим объектом
Здравствуйте. Я начинающий в программировании. Пытаюсь использовать Serilog в своей программе. Хочу использовать сразу несколько...

Serilog. Как логировать , используя внедрение зависимостей
Всем добрый день! Цель: записывать логи в файл формата {"имя контроллера"-"дата"}. То есть на каждый контроллер свой файл...

старт
что-то не могу понять Всем привет! Коллеги подскажите - набросал простецкое wpf браузерное приложение, сделал Publish если...

18
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 20:42
gazed, LoggingLevelSwitch это для динамического изменения детализации логирования в рантайме.
Например, посыпались ошибки на проде внезапно, сменили switch c Information на Debug, собрали логи посмотрели, поправили.
А вам нужно отдельные синки на каждый лог ятп. Это делается фильтрами, насколько я помню.

Добавлено через 1 минуту
Цитата Сообщение от IamRain Посмотреть сообщение
LoggingLevelSwitch это для динамического изменения детализации логирования в рантайме.
То есть бессмысленно иметь больше одного экземпляра свитчей.

Добавлено через 10 минут
На базе weatherforecast webapi:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Debug);
 
 
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Debug))
        .WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100)
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Warning))
    .WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100)
#if DEBUG
    .WriteTo.Console(levelSwitch: levelSwitch)
#endif
    .CreateLogger();
 
Log.Logger.Debug("Debug: before creating builder");
Log.Logger.Information("Information: before creating builder");
Log.Logger.Warning("Warning: before creating builder");
 
var builder = WebApplication.CreateBuilder(args);
 
Log.Logger.Debug("Debug: after creating builder");
Log.Logger.Information("Information: after creating builder");
Log.Logger.Warning("Warning: after creating builder");
1
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 20:42  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
LoggingLevelSwitch это для динамического изменения детализации логирования в рантайме.
Это я понимаю, решил из примера не убирать, вдруг из-за них проблема, хотя пробовал и без них.
Цитата Сообщение от IamRain Посмотреть сообщение
То есть бессмысленно иметь больше одного экземпляра свитчей.
Если для разных типов приёмников, то не бессмысленно. Но в принципе не важно, так как суть проблемы не в этом.

Цитата Сообщение от IamRain Посмотреть сообщение
А вам нужно отдельные синки на каждый лог ятп. Это делается фильтрами, насколько я помню.
Вот это странно, не понимаю почему добавляешь 2 идентичных sinks а работают совсем по разному, при чём второй не важно где находится в первой или второй, не принимает вообще логи из Log.Logger.Метод("текст лога");


Похоже нужно создать отдельно от serilog что-то, что сохранится, если приложение не стартонуло.
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 20:54
Насколько я понимаю, это называется вложенные логгеры (просто потому что fluent api Write.To.Logger - один основной, но вложенных несколько).
И вроде как каждый из вложенных может иметь свои синки (тут не уверен), вообщем таков дизайн.

Добавлено через 9 минут
Поправочка, вот так должно выглядеть:
C#
1
2
3
4
5
6
7
8
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Debug).WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100))
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Warning).WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100))
#if DEBUG
    .WriteTo.Console(levelSwitch: levelSwitch)
#endif
    .CreateLogger();
Осталось только проверить на Manjaro содержимое sqlite табличек..
1
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 20:56  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var levelSwitch = new LoggingLevelSwitch(LogEventLevel.Debug);
 
 
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Debug))
        .WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100)
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Warning))
    .WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100)
#if DEBUG
    .WriteTo.Console(levelSwitch: levelSwitch)
#endif
    .CreateLogger();
 
Log.Logger.Debug("Debug: before creating builder");
Log.Logger.Information("Information: before creating builder");
Log.Logger.Warning("Warning: before creating builder");
Взял этот код и запустил в новом приложении, обе базы идентичны по 6 сообщений, хотя там указаны разные уровни.
Очень странно этот serilog себя ведёт.
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 20:57
Так по короче (без фильтров):
C#
1
2
3
4
5
6
7
8
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.Logger(lc => lc.WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100), LogEventLevel.Debug)
    .WriteTo.Logger(lc => lc.WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100), LogEventLevel.Warning)
#if DEBUG
    .WriteTo.Console(levelSwitch: levelSwitch)
#endif
    .CreateLogger();[S][/S]
1
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 21:01  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
Поправочка, вот так должно выглядеть:
Да, так поведение изменилось.
Теперь в одну сохраняются ТОЛЬКО Debug, в другую ТОЛЬКО Warning.
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 21:04
Цитата Сообщение от IamRain Посмотреть сообщение
Так по короче (без фильтров)
Вот это работает:
Миниатюры
Serilog до старта приложения  
1
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 21:05
А в первой все 6 записей:
Миниатюры
Serilog до старта приложения  
1
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 21:14
Цитата Сообщение от gazed Посмотреть сообщение
Теперь в одну сохраняются ТОЛЬКО Debug, в другую ТОЛЬКО Warning.
Минимальный уровень логирования - то есть должно все начиная с указанного уровня детализации и выше (по значения энама).
То есть у себя я наблюдаю ожидаемое поведение.

Добавлено через 1 минуту
Для справки:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public enum LogEventLevel
  {
    /// <summary>
    /// Anything and everything you might want to know about
    /// a running block of code.
    /// </summary>
    Verbose,
    /// <summary>
    /// Internal system events that aren't necessarily
    /// observable from the outside.
    /// </summary>
    Debug,
    /// <summary>
    /// The lifeblood of operational intelligence - things
    /// happen.
    /// </summary>
    Information,
    /// <summary>Service is degraded or endangered.</summary>
    Warning,
    /// <summary>
    /// Functionality is unavailable, invariants are broken
    /// or data is lost.
    /// </summary>
    Error,
    /// <summary>
    /// If you have a pager, it goes off when one of these
    /// occurs.
    /// </summary>
    Fatal,
  }
Добавлено через 1 минуту
То есть если бы мы писали Log.Logger.Verbose(); то это не попало бы ни в одну базу.

Добавлено через 3 минуты
Ятп по дизайну логгер один в Serilog (поскольку один LoggerConfiguration), но у него есть nested loggers.
1
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 21:17  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
Минимальный уровень логирования - то есть должно все начиная с указанного уровня детализации и выше (по значения энама).
То есть у себя я наблюдаю ожидаемое поведение.
C#
1
2
3
4
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Debug)
        .WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100))
    .WriteTo.Logger(lc => lc.Filter.ByIncludingOnly(le => le.Level == LogEventLevel.Warning)
        .WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100))
Если так, то пишет только тот что указан, в Logs.db3 Debug, а в Logs2.db3 Warning.

C#
1
2
    .WriteTo.Logger(lc => lc.WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100), LogEventLevel.Debug)
    .WriteTo.Logger(lc => lc.WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100), LogEventLevel.Warning)
А так пишет начиная с указанного уровня, то есть Debug в данном случае, пишет всё в Logs.db3
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 21:18
И лучше вам прикрутить это к встроенному интерфейсу ILogger<T> - тут автоматически прикручивается SourceContext (из параметра <T> типа) и можно дополнительные плюшки использовать (типа Scopes). А не писать через статику Log.Logger - такое только в точке входа норм.
1
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 21:20  [ТС]
IamRain, а с .CreateBootstrapLogger(); не пробовали?

В примерах почему-то если используется .CreateBootstrapLogger();, то последующая загрузка sinks идёт из конфигурационного файла.
Может именно под конфиг и заточено, а я пытаюсь настроить кодом
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 21:21
Цитата Сообщение от gazed Посмотреть сообщение
Если так, то пишет только тот что указан,
Ну да, имя метода так и говорит: ByIncludingOnly. То есть это ожидаемое, я просто пример кода со SO стянул.
1
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 21:22  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
И лучше вам прикрутить это к встроенному интерфейсу ILogger<T>
Да, в проекте везде используется стандартный логгер от MS.

Вот с самим стартом проблема.
Хочу неудачные старты приложений сохранять в Seq.
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 22:09
Цитата Сообщение от gazed Посмотреть сообщение
а с .CreateBootstrapLogger(); не пробовали?
Ятп именно этот метод нужен для двухэтапной инициализации логгера через конфигурацию.
То есть перед вызовом метода var app = builder.Build(); сама конфигурация еще не готова.
А вам ведь нужно
Цитата Сообщение от gazed Посмотреть сообщение
чтобы собрать логи на момент создания WebApplication.
Почему бы так не оставить? Главное чтобы в DI это дальше работало.

Добавлено через 56 секунд
Доки: тыц.

Добавлено через 21 минуту
Цитата Сообщение от gazed Посмотреть сообщение
а с .CreateBootstrapLogger(); не пробовали?
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Log.Logger = new LoggerConfiguration()
    
    .MinimumLevel.ControlledBy(levelSwitch)
    .WriteTo.Logger(lc => lc.WriteTo.SQLite("Logs.db3", maxDatabaseSize: 100), LogEventLevel.Debug)
    .WriteTo.Logger(lc => lc.WriteTo.SQLite("Logs2.db3", maxDatabaseSize: 100), LogEventLevel.Warning)
#if DEBUG
    .WriteTo.Console(levelSwitch: levelSwitch)
#endif
    .CreateBootstrapLogger();
 
Log.Logger.Debug("Debug: before creating builder");
Log.Logger.Information("Information: before creating builder");
Log.Logger.Warning("Warning: before creating builder");
 
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSerilog(x => 
    x.ReadFrom.Configuration(builder.Configuration));
Работает ожидаемо. Однако я не до конца понимаю, как работает этот BootstrapLogger, возможно он дает возможность слить fluent конфигурацию и конфигурацию из файла. И непонятно, как это будет работать, если в json-конфигурации что-то будет (он у меня пустой).

Последний раз когда я это делал (год назад), в коде был только вызов x.ReadFrom.Configuration(builder.Configuration)), а сам конфиг - целая портянка - ручками один раз писалась в json-файле.
И сейчас мне лень перечитывать документацию.

Добавлено через 5 минут
Надо просто один раз вдумчиво прочитать всю страничку.

Добавлено через 10 минут
Цитата Сообщение от IamRain Посмотреть сообщение
озможно он дает возможность слить fluent конфигурацию и конфигурацию из файла.
Видимо, примерно так и есть (public static ReloadableLogger CreateBootstrapLogger( this LoggerConfiguration loggerConfiguration)):
Create a ReloadableLogger for use during host bootstrapping. The UseSerilog(this IHostBuilder, Action<HostBuilderContext,IServiceProvid er,LoggerConfiguration>, bool, bool) configuration overload will detect when Logger is set to a ReloadableLogger instance, and reconfigure/ freeze it so that ILoggers created during host bootstrapping continue to work once logger configuration (with access to host services) is completed.
0
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
08.02.2025, 22:35  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
builder.Services.AddSerilog(x =>
    x.ReadFrom.Configuration(builder.Configu ration));
Да, в справке только конфиг указан, но не получается Seq там зарегистрировать, чтобы работал, как при обычной регистрации без .CreateBootstrapLogger();
0
 Аватар для IamRain
4693 / 2701 / 734
Регистрация: 02.08.2011
Сообщений: 7,218
08.02.2025, 23:15
Цитата Сообщение от gazed Посмотреть сообщение
но не получается Seq там зарегистрировать, чтобы работал, как при обычной регистрации без .CreateBootstrapLogger();
Конфиг работает способом очень похожим на рефлексию ятп - имена параметров в методах расширения должны совпадать с настройками в файле конфигурации.
1. Seq docs
2. SO sample:
{
"Serilog": {
"Using": [ "Serilog.Sinks.Seq" ],
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"Enrich": [ "FromLogContext" ],
"WriteTo": [
{
"Name": "Seq",
"Args": {
"apiKey": "iVPcw1...",
"serverUrl": "http://localhost:5341"
}
}
]
},
...
}
0
3 / 3 / 1
Регистрация: 24.02.2024
Сообщений: 131
09.02.2025, 07:20  [ТС]
Цитата Сообщение от IamRain Посмотреть сообщение
Конфиг работает способом очень похожим на рефлексию ятп - имена параметров в методах расширения должны совпадать с настройками в файле конфигурации.
Я понимаю как он работает.
Не хочу чувствительную информацию хранить в открытом доступе, и не хочу зависеть сильно от конфига.
Например SQLite sinks нет смысла хранить в json, так как он настраивается относительно программы в папке Logs, поэтому его лучше настроить внутри, и не загрязнять json.
Seq нужно хранить только url, ключ будет внутри, а уровень логирования будет динамический, тоже его нет смысла хранить в конфиге.

То есть цель такая, чтобы в appsettings.json не был Франкенштейном непонятным, а хранил только минимально необходимую информацию, например url хоста, url seq, но не уровни логирования и т.д.

Как минимум пользователь удалил блок Serilog, и всё приложение осталось без логов.
Если хранить все настройки логирования, то как потом пользователю объяснить, чтобы он изменил уровень логирования в appsettings.json с Warning на Debug? Или есть и для этого специнструменты?

Добавлено через 2 минуты
Цитата Сообщение от IamRain Посмотреть сообщение
"Default": "Debug",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
А если учесть override так пользователю вообще непонятно какой Warning нужно изменить
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
09.02.2025, 07:20
Помогаю со студенческими работами здесь

Telerik. Таблицы. Старт.
Вобщем хочу использовать таблицу от Telerik. Скачал Extensions for ASP.NET MVC Q1 2011 SP1 , установил. А что дальше делать не знаю. ...

Медленный старт приложения
Есть примитивнейшее приложение, в котором только один лейбл есть и все, и это приложение стартует секунд 5. Это особенности WPF или есть...

Почему программа наглухо зависает после нажатия старт?
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using...

Как создать песочницу для приложени(й)я?
Необходимо создать песочницу для исполняемого *.exe файла. У файла должен быть доступ к папке, в которой он находится, а так же, ко...

Старт приложения
У меня есть большое приложение на С# и вот старт приложения занимает очень много времени порядка 3 минут. Помимо C# имеются все, есть и...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
19
Ответ Создать тему
Новые блоги и статьи
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
Фото: Daniel Greenwood
kumehtar 13.11.2025
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru