From: Leon Friedrich <60421075+ElectroJr@users.noreply.github.com> Date: Fri, 5 Sep 2025 12:22:49 +0000 (+1200) Subject: Rejig LogStringHandler (#30706) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=828b1f2044900eca9122c710cc6025fcc08c1291;p=space-station-14.git Rejig LogStringHandler (#30706) * Rejig LogStringHandler * Fix session logs * Fix properly * comments * IAsType support * Fix mind logs * Fix mind logging AGAIN --------- Co-authored-by: PJB3005 --- diff --git a/Content.Server/Administration/Logs/AdminLogManager.Json.cs b/Content.Server/Administration/Logs/AdminLogManager.Json.cs index 9e6274a493..a0a3b920bd 100644 --- a/Content.Server/Administration/Logs/AdminLogManager.Json.cs +++ b/Content.Server/Administration/Logs/AdminLogManager.Json.cs @@ -2,9 +2,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using Content.Server.Administration.Logs.Converters; -using Robust.Server.GameObjects; -using Robust.Shared.Map; -using Robust.Shared.Player; +using Robust.Shared.Collections; namespace Content.Server.Administration.Logs; @@ -22,55 +20,25 @@ public sealed partial class AdminLogManager PropertyNamingPolicy = NamingPolicy }; + var interfaces = new ValueList(); + foreach (var converter in _reflection.FindTypesWithAttribute()) { var instance = _typeFactory.CreateInstance(converter); - (instance as IAdminLogConverter)?.Init(_dependencies); + if (instance is IAdminLogConverter converterInterface) + { + interfaces.Add(converterInterface); + converterInterface.Init(_dependencies); + } _jsonOptions.Converters.Add(instance); } - var converterNames = _jsonOptions.Converters.Select(converter => converter.GetType().Name); - _sawmill.Debug($"Admin log converters found: {string.Join(" ", converterNames)}"); - } - - private (JsonDocument Json, HashSet Players) ToJson( - Dictionary properties) - { - var players = new HashSet(); - var parsed = new Dictionary(); - - foreach (var key in properties.Keys) + foreach (var @interface in interfaces) { - var value = properties[key]; - value = value switch - { - ICommonSession player => new SerializablePlayer(player), - EntityCoordinates entityCoordinates => new SerializableEntityCoordinates(_entityManager, entityCoordinates), - _ => value - }; - - var parsedKey = NamingPolicy.ConvertName(key); - parsed.Add(parsedKey, value); - - var entityId = properties[key] switch - { - EntityUid id => id, - EntityStringRepresentation rep => rep.Uid, - ICommonSession {AttachedEntity: {Valid: true}} session => session.AttachedEntity, - IComponent component => component.Owner, - _ => null - }; - - if (_entityManager.TryGetComponent(entityId, out ActorComponent? actor)) - { - players.Add(actor.PlayerSession.UserId.UserId); - } - else if (value is SerializablePlayer player) - { - players.Add(player.Player.UserId.UserId); - } + @interface.Init2(_jsonOptions); } - return (JsonSerializer.SerializeToDocument(parsed, _jsonOptions), players); + var converterNames = _jsonOptions.Converters.Select(converter => converter.GetType().Name); + _sawmill.Debug($"Admin log converters found: {string.Join(" ", converterNames)}"); } } diff --git a/Content.Server/Administration/Logs/AdminLogManager.cs b/Content.Server/Administration/Logs/AdminLogManager.cs index 600311a651..dd8c473ce9 100644 --- a/Content.Server/Administration/Logs/AdminLogManager.cs +++ b/Content.Server/Administration/Logs/AdminLogManager.cs @@ -25,7 +25,6 @@ namespace Content.Server.Administration.Logs; public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogManager { [Dependency] private readonly IConfigurationManager _configuration = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IServerDbManager _db = default!; [Dependency] private readonly IGameTiming _timing = default!; @@ -72,7 +71,6 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa // CVars private bool _metricsEnabled; - private bool _enabled; private TimeSpan _queueSendDelay; private int _queueMax; private int _preRoundQueueMax; @@ -103,7 +101,7 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa _configuration.OnValueChanged(CVars.MetricsEnabled, value => _metricsEnabled = value, true); _configuration.OnValueChanged(CCVars.AdminLogsEnabled, - value => _enabled = value, true); + value => Enabled = value, true); _configuration.OnValueChanged(CCVars.AdminLogsQueueSendDelay, value => _queueSendDelay = TimeSpan.FromSeconds(value), true); _configuration.OnValueChanged(CCVars.AdminLogsQueueMax, @@ -123,6 +121,12 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa } } + public override string ConvertName(string name) + { + // JsonNamingPolicy is not whitelisted by the sandbox. + return NamingPolicy.ConvertName(name); + } + public async Task Shutdown() { if (!_logQueue.IsEmpty) @@ -292,8 +296,17 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa } } - private void Add(LogType type, LogImpact impact, string message, JsonDocument json, HashSet players) + public override void Add(LogType type, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("")] ref LogStringHandler handler) { + Add(type, LogImpact.Medium, ref handler); + } + + public override void Add(LogType type, LogImpact impact, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("")] ref LogStringHandler handler) + { + var message = handler.ToStringAndClear(); + if (!Enabled) + return; + var preRound = _runLevel == GameRunLevel.PreRoundLobby; var count = preRound ? _preRoundLogQueue.Count : _logQueue.Count; if (count >= _dropThreshold) @@ -302,6 +315,10 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa return; } + var json = JsonSerializer.SerializeToDocument(handler.Values, _jsonOptions); + var id = NextLogId; + var players = GetPlayers(handler.Values, id); + // PostgreSQL does not support storing null chars in text values. if (message.Contains('\0')) { @@ -311,31 +328,85 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa var log = new AdminLog { - Id = NextLogId, + Id = id, RoundId = _currentRoundId, Type = type, Impact = impact, Date = DateTime.UtcNow, Message = message, Json = json, - Players = new List(players.Count) + Players = players, }; - var adminLog = false; - var adminSys = _entityManager.SystemOrNull(); - var logMessage = message; + DoAdminAlerts(players, message, impact); - foreach (var id in players) + if (preRound) { - var player = new AdminLogPlayer + _preRoundLogQueue.Enqueue(log); + } + else + { + _logQueue.Enqueue(log); + CacheLog(log); + } + } + + private List GetPlayers(Dictionary values, int logId) + { + List players = new(); + foreach (var value in values.Values) + { + switch (value) { - LogId = log.Id, - PlayerUserId = id - }; + case SerializablePlayer player: + AddPlayer(players, player.UserId, logId); + continue; + + case EntityStringRepresentation rep: + if (rep.Session is {} session) + AddPlayer(players, session.UserId.UserId, logId); + continue; - log.Players.Add(player); + case IAdminLogsPlayerValue playerValue: + foreach (var player in playerValue.Players) + { + AddPlayer(players, player, logId); + } - if (adminSys != null) + break; + } + } + + return players; + } + + private void AddPlayer(List players, Guid user, int logId) + { + // The majority of logs have a single player, or maybe two. Instead of allocating a List and + // HashSet, we just iterate over the list to check for duplicates. + foreach (var player in players) + { + if (player.PlayerUserId == user) + return; + } + + players.Add(new AdminLogPlayer + { + LogId = logId, + PlayerUserId = user + }); + } + + private void DoAdminAlerts(List players, string message, LogImpact impact) + { + var adminLog = true; + var logMessage = message; + + foreach (var player in players) + { + var id = player.PlayerUserId; + + if (EntityManager.TrySystem(out AdminSystem? adminSys)) { var cachedInfo = adminSys.GetCachedPlayerInfo(new NetUserId(id)); if (cachedInfo != null && cachedInfo.Antag) @@ -372,35 +443,6 @@ public sealed partial class AdminLogManager : SharedAdminLogManager, IAdminLogMa if (adminLog) _chat.SendAdminAlert(logMessage); - - if (preRound) - { - _preRoundLogQueue.Enqueue(log); - } - else - { - _logQueue.Enqueue(log); - CacheLog(log); - } - } - - public override void Add(LogType type, LogImpact impact, ref LogStringHandler handler) - { - if (!_enabled) - { - handler.ToStringAndClear(); - return; - } - - var (json, players) = ToJson(handler.Values); - var message = handler.ToStringAndClear(); - - Add(type, impact, message, json, players); - } - - public override void Add(LogType type, ref LogStringHandler handler) - { - Add(type, LogImpact.Medium, ref handler); } public async Task> All(LogFilter? filter = null, Func>? listProvider = null) diff --git a/Content.Server/Administration/Logs/Converters/AdminLogConverter.cs b/Content.Server/Administration/Logs/Converters/AdminLogConverter.cs index 7eaab9ba28..778f84c1ac 100644 --- a/Content.Server/Administration/Logs/Converters/AdminLogConverter.cs +++ b/Content.Server/Administration/Logs/Converters/AdminLogConverter.cs @@ -6,6 +6,13 @@ namespace Content.Server.Administration.Logs.Converters; public interface IAdminLogConverter { void Init(IDependencyCollection dependencies); + + /// + /// Called after all converters have been added to the . + /// + void Init2(JsonSerializerOptions options) + { + } } public abstract class AdminLogConverter : JsonConverter, IAdminLogConverter @@ -14,6 +21,10 @@ public abstract class AdminLogConverter : JsonConverter, IAdminLogConverte { } + public virtual void Init2(JsonSerializerOptions options) + { + } + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { throw new NotSupportedException(); diff --git a/Content.Server/Administration/Logs/Converters/EntityCoordinatesConverter.cs b/Content.Server/Administration/Logs/Converters/EntityCoordinatesConverter.cs index fb5c6a6fe5..3a0ffeb758 100644 --- a/Content.Server/Administration/Logs/Converters/EntityCoordinatesConverter.cs +++ b/Content.Server/Administration/Logs/Converters/EntityCoordinatesConverter.cs @@ -6,7 +6,7 @@ using Robust.Shared.Map.Components; namespace Content.Server.Administration.Logs.Converters; [AdminLogConverter] -public sealed class EntityCoordinatesConverter : AdminLogConverter +public sealed class EntityCoordinatesConverter : AdminLogConverter { // System.Text.Json actually keeps hold of your JsonSerializerOption instances in a cache on .NET 7. // Use a weak reference to avoid holding server instances live too long in integration tests. @@ -17,15 +17,16 @@ public sealed class EntityCoordinatesConverter : AdminLogConverter(dependencies.Resolve()); } - public void Write(Utf8JsonWriter writer, SerializableEntityCoordinates value, JsonSerializerOptions options, IEntityManager entities) + public void Write(Utf8JsonWriter writer, EntityCoordinates value, JsonSerializerOptions options, IEntityManager entities) { writer.WriteStartObject(); - WriteEntityInfo(writer, value.EntityUid, entities, "parent"); + WriteEntityInfo(writer, value.EntityId, entities, "parent"); writer.WriteNumber("x", value.X); writer.WriteNumber("y", value.Y); - if (value.MapUid.HasValue) + var mapUid = value.GetMapUid(entities); + if (mapUid.HasValue) { - WriteEntityInfo(writer, value.MapUid.Value, entities, "map"); + WriteEntityInfo(writer, mapUid.Value, entities, "map"); } writer.WriteEndObject(); } @@ -33,7 +34,7 @@ public sealed class EntityCoordinatesConverter : AdminLogConverter().GetMap(coordinates); - } -} diff --git a/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs b/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs index 39d34e5f18..9a92a2cb46 100644 --- a/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs +++ b/Content.Server/Administration/Logs/Converters/EntityStringRepresentationConverter.cs @@ -1,6 +1,5 @@ using System.Text.Json; using Content.Server.Administration.Managers; -using Robust.Server.Player; namespace Content.Server.Administration.Logs.Converters; @@ -24,7 +23,7 @@ public sealed class EntityStringRepresentationConverter : AdminLogConverter +{ + private JsonConverter _converter = null!; + + public override void Init2(JsonSerializerOptions options) + { + base.Init2(options); + + _converter = (JsonConverter) + options.GetConverter(typeof(EntityStringRepresentation)); + } + + public override void Write(Utf8JsonWriter writer, MindStringRepresentation value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + if (value.OwnedEntity is { } owned) + { + writer.WritePropertyName("owned"); + _converter.Write(writer, owned, options); + } + + if (value.Player is { } player) + { + writer.WriteString("player", player); + writer.WriteBoolean("present", value.PlayerPresent); + } + + writer.WriteEndObject(); + } +} diff --git a/Content.Server/Administration/Logs/Converters/PlayerSessionConverter.cs b/Content.Server/Administration/Logs/Converters/PlayerSessionConverter.cs index c1567448cc..d1a009b8cd 100644 --- a/Content.Server/Administration/Logs/Converters/PlayerSessionConverter.cs +++ b/Content.Server/Administration/Logs/Converters/PlayerSessionConverter.cs @@ -1,45 +1,23 @@ using System.Text.Json; -using Robust.Shared.Player; +using Content.Shared.Administration.Logs; namespace Content.Server.Administration.Logs.Converters; [AdminLogConverter] public sealed class PlayerSessionConverter : AdminLogConverter { - // System.Text.Json actually keeps hold of your JsonSerializerOption instances in a cache on .NET 7. - // Use a weak reference to avoid holding server instances live too long in integration tests. - private WeakReference _entityManager = default!; - - public override void Init(IDependencyCollection dependencies) - { - _entityManager = new WeakReference(dependencies.Resolve()); - } - public override void Write(Utf8JsonWriter writer, SerializablePlayer value, JsonSerializerOptions options) { writer.WriteStartObject(); - if (value.Player.AttachedEntity is {Valid: true} playerEntity) + if (value.Uid is {Valid: true} playerEntity) { - if (!_entityManager.TryGetTarget(out var entityManager)) - throw new InvalidOperationException("EntityManager got garbage collected!"); - - writer.WriteNumber("id", (int) value.Player.AttachedEntity); - writer.WriteString("name", entityManager.GetComponent(playerEntity).EntityName); + writer.WriteNumber("id", playerEntity.Id); + writer.WriteString("name", value.Name); } - writer.WriteString("player", value.Player.UserId.UserId); + writer.WriteString("player", value.UserId); writer.WriteEndObject(); } } - -public readonly struct SerializablePlayer -{ - public readonly ICommonSession Player; - - public SerializablePlayer(ICommonSession player) - { - Player = player; - } -} diff --git a/Content.Server/Botany/Systems/PlantHolderSystem.cs b/Content.Server/Botany/Systems/PlantHolderSystem.cs index e38c742fa2..caa796efe2 100644 --- a/Content.Server/Botany/Systems/PlantHolderSystem.cs +++ b/Content.Server/Botany/Systems/PlantHolderSystem.cs @@ -2,6 +2,7 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Botany.Components; using Content.Server.Hands.Systems; using Content.Server.Popups; +using Content.Shared.Administration.Logs; using Content.Shared.Chemistry.EntitySystems; using Content.Shared.Atmos; using Content.Shared.Botany; @@ -886,7 +887,7 @@ public sealed class PlantHolderSystem : EntitySystem foreach (var entry in _solutionContainerSystem.RemoveEachReagent(component.SoilSolution.Value, amt)) { var reagentProto = _prototype.Index(entry.Reagent.Prototype); - reagentProto.ReactionPlant(uid, entry, solution); + reagentProto.ReactionPlant(uid, entry, solution, EntityManager, _random, _adminLogger); } } diff --git a/Content.Shared/Administration/Logs/IAdminLogsPlayerValue.cs b/Content.Shared/Administration/Logs/IAdminLogsPlayerValue.cs new file mode 100644 index 0000000000..e99b6b6034 --- /dev/null +++ b/Content.Shared/Administration/Logs/IAdminLogsPlayerValue.cs @@ -0,0 +1,11 @@ +using Robust.Shared.Network; + +namespace Content.Shared.Administration.Logs; + +/// +/// Interface implemented by admin log values that contain player references. +/// +public interface IAdminLogsPlayerValue +{ + IEnumerable Players { get; } +} diff --git a/Content.Shared/Administration/Logs/ISharedAdminLogManager.cs b/Content.Shared/Administration/Logs/ISharedAdminLogManager.cs index d00d6a38de..5e528fe1cd 100644 --- a/Content.Shared/Administration/Logs/ISharedAdminLogManager.cs +++ b/Content.Shared/Administration/Logs/ISharedAdminLogManager.cs @@ -1,10 +1,19 @@ -using Content.Shared.Database; +using System.Runtime.CompilerServices; +using Content.Shared.Database; namespace Content.Shared.Administration.Logs; public interface ISharedAdminLogManager { - void Add(LogType type, LogImpact impact, ref LogStringHandler handler); + public bool Enabled { get; } - void Add(LogType type, ref LogStringHandler handler); + // JsonNamingPolicy is not whitelisted by the sandbox. + public string ConvertName(string name); + + // Required for the log string interpolation handler to access ToPrettyString() + public IEntityManager EntityManager { get; } + + void Add(LogType type, LogImpact impact, [InterpolatedStringHandlerArgument("")] ref LogStringHandler handler); + + void Add(LogType type, [InterpolatedStringHandlerArgument("")] ref LogStringHandler handler); } diff --git a/Content.Shared/Administration/Logs/LogStringHandler.cs b/Content.Shared/Administration/Logs/LogStringHandler.cs index 8d06c448a5..4dbbcf089c 100644 --- a/Content.Shared/Administration/Logs/LogStringHandler.cs +++ b/Content.Shared/Administration/Logs/LogStringHandler.cs @@ -1,29 +1,33 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Robust.Shared.Player; +using Robust.Shared.Toolshed.TypeParsers; namespace Content.Shared.Administration.Logs; [InterpolatedStringHandler] +[SuppressMessage("ReSharper", "MethodOverloadWithOptionalParameter")] public ref struct LogStringHandler { + public readonly ISharedAdminLogManager Logger; private DefaultInterpolatedStringHandler _handler; public readonly Dictionary Values; - public LogStringHandler(int literalLength, int formattedCount) + public LogStringHandler(int literalLength, int formattedCount, ISharedAdminLogManager logger, out bool isEnabled) { - _handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount); - Values = new Dictionary(); - } + isEnabled = logger.Enabled; + if (!isEnabled) + { + Values = default!; + Logger = default!; + return; + } - public LogStringHandler(int literalLength, int formattedCount, IFormatProvider? provider) - { - _handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider); - Values = new Dictionary(); - } + _handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount); - public LogStringHandler(int literalLength, int formattedCount, IFormatProvider? provider, Span initialBuffer) - { - _handler = new DefaultInterpolatedStringHandler(literalLength, formattedCount, provider, initialBuffer); - Values = new Dictionary(); + // TODO LOGGING Dictionary pool? + Values = new Dictionary(formattedCount); + Logger = logger; } private void AddFormat(string? format, T value, string? argument = null) @@ -31,15 +35,13 @@ public ref struct LogStringHandler if (format == null) { if (argument == null) - { return; - } format = argument[0] == '@' ? argument[1..] : argument; } - if (Values.TryAdd(format, value) || - Values[format] == (object?) value) + if (Values.TryAdd(Logger.ConvertName(format), value) + || Values[format] is T val && val.Equals(value) ) { return; } @@ -48,7 +50,8 @@ public ref struct LogStringHandler var i = 2; format = $"{originalFormat}_{i}"; - while (!Values.TryAdd(format, value)) + while (!Values.TryAdd(Logger.ConvertName(format), value) + || Values[format] is T val2 && val2.Equals(value)) { format = $"{originalFormat}_{i}"; i++; @@ -60,30 +63,176 @@ public ref struct LogStringHandler _handler.AppendLiteral(value); } + #region EntityUid + + public void AppendFormatted(EntityUid value, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), argument); + } + + public void AppendFormatted(EntityUid value, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), format, argument); + } + + public void AppendFormatted(EntityUid value, int alignment, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, argument); + } + + public void AppendFormatted(EntityUid value, int alignment, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, format, argument); + } + + public void AppendFormatted(EntityUid? value, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), argument); + } + + public void AppendFormatted(EntityUid? value, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), format, argument); + } + + public void AppendFormatted(EntityUid? value, int alignment, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, argument); + } + + public void AppendFormatted(EntityUid? value, int alignment, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, format, argument); + } + + #endregion + + #region NetEntity + + public void AppendFormatted(NetEntity value, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), argument); + } + + public void AppendFormatted(NetEntity value, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), format, argument); + } + + public void AppendFormatted(NetEntity value, int alignment, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, argument); + } + + public void AppendFormatted(NetEntity value, int alignment, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, format, argument); + } + + public void AppendFormatted(NetEntity? value, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), argument); + } + + public void AppendFormatted(NetEntity? value, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), format, argument); + } + + public void AppendFormatted(NetEntity? value, int alignment, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, argument); + } + + public void AppendFormatted(NetEntity? value, int alignment, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + AppendFormatted(Logger.EntityManager.ToPrettyString(value), alignment, format, argument); + } + #endregion + + #region Player + + public void AppendFormatted(ICommonSession? value, [CallerArgumentExpression("value")] string? argument = null) + { + SerializablePlayer? player = value == null ? null : new(value, Logger.EntityManager); + AddFormat(null, player, argument); + _handler.AppendFormatted(value); + } + + public void AppendFormatted(ICommonSession? value, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + SerializablePlayer? player = value == null ? null : new(value, Logger.EntityManager); + AddFormat(null, player, argument); + _handler.AppendFormatted(value, format); + } + + public void AppendFormatted(ICommonSession? value, int alignment, [CallerArgumentExpression("value")] string? argument = null) + { + SerializablePlayer? player = value == null ? null : new(value, Logger.EntityManager); + AddFormat(null, player, argument); + _handler.AppendFormatted(value, alignment); + } + + public void AppendFormatted(ICommonSession? value, int alignment, string? format, [CallerArgumentExpression("value")] string? argument = null) + { + SerializablePlayer? player = value == null ? null : new(value, Logger.EntityManager); + AddFormat(null, player, argument); + _handler.AppendFormatted(value, alignment, format); + } + #endregion + + #region Generic + public void AppendFormatted(T value, [CallerArgumentExpression("value")] string? argument = null) { + if (value is IAsType ent) + { + AppendFormatted(ent.AsType(), argument); + return; + } + AddFormat(null, value, argument); _handler.AppendFormatted(value); } public void AppendFormatted(T value, string? format, [CallerArgumentExpression("value")] string? argument = null) { + if (value is IAsType ent) + { + AppendFormatted(ent.AsType(), format, argument); + return; + } + AddFormat(format, value, argument); _handler.AppendFormatted(value, format); } public void AppendFormatted(T value, int alignment, [CallerArgumentExpression("value")] string? argument = null) { + if (value is IAsType ent) + { + AppendFormatted(ent.AsType(), alignment, argument); + return; + } + AddFormat(null, value, argument); _handler.AppendFormatted(value, alignment); } public void AppendFormatted(T value, int alignment, string? format, [CallerArgumentExpression("value")] string? argument = null) { + if (value is IAsType ent) + { + AppendFormatted(ent.AsType(), alignment, format, argument); + return; + } + AddFormat(format, value, argument); _handler.AppendFormatted(value, alignment, format); } + #endregion + public void AppendFormatted(ReadOnlySpan value) { _handler.AppendFormatted(value); @@ -116,7 +265,23 @@ public ref struct LogStringHandler public string ToStringAndClear() { - Values.Clear(); return _handler.ToStringAndClear(); } } + +public readonly struct SerializablePlayer +{ + public readonly Guid UserId; + public readonly EntityUid? Uid; + public readonly string? Name; + + public SerializablePlayer(ICommonSession player, IEntityManager entityManager) + { + UserId = player.UserId.UserId; + if (player.AttachedEntity is not {} uid) + return; + + Uid = uid; + Name = entityManager.GetComponentOrNull(uid)?.EntityName; + } +} diff --git a/Content.Shared/Administration/Logs/SharedAdminLogManager.cs b/Content.Shared/Administration/Logs/SharedAdminLogManager.cs index 8641fa6e02..ada2b61638 100644 --- a/Content.Shared/Administration/Logs/SharedAdminLogManager.cs +++ b/Content.Shared/Administration/Logs/SharedAdminLogManager.cs @@ -5,6 +5,13 @@ namespace Content.Shared.Administration.Logs; [Virtual] public class SharedAdminLogManager : ISharedAdminLogManager { + [Dependency] private readonly IEntityManager _entityManager = default!; + public IEntityManager EntityManager => _entityManager; + + public bool Enabled { get; protected set; } + + public virtual string ConvertName(string name) => name; + public virtual void Add(LogType type, LogImpact impact, ref LogStringHandler handler) { // noop diff --git a/Content.Shared/Administration/Logs/SharedAdminLogSystem.cs b/Content.Shared/Administration/Logs/SharedAdminLogSystem.cs deleted file mode 100644 index eb3f8ff98f..0000000000 --- a/Content.Shared/Administration/Logs/SharedAdminLogSystem.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Content.Shared.Database; - -namespace Content.Shared.Administration.Logs; - -public abstract class SharedAdminLogSystem : EntitySystem -{ - public virtual void Add(LogType type, LogImpact impact, ref LogStringHandler handler) - { - // noop - } - - public virtual void Add(LogType type, ref LogStringHandler handler) - { - // noop - } -} diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index 7689a27cd0..4224fa4bc7 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -190,14 +190,17 @@ namespace Content.Shared.Chemistry.Reagent return removed; } - public void ReactionPlant(EntityUid? plantHolder, ReagentQuantity amount, Solution solution) + public void ReactionPlant(EntityUid? plantHolder, + ReagentQuantity amount, + Solution solution, + EntityManager entityManager, + IRobustRandom random, + ISharedAdminLogManager logger) { if (plantHolder == null) return; - var entMan = IoCManager.Resolve(); - var random = IoCManager.Resolve(); - var args = new EntityEffectReagentArgs(plantHolder.Value, entMan, null, solution, amount.Quantity, this, null, 1f); + var args = new EntityEffectReagentArgs(plantHolder.Value, entityManager, null, solution, amount.Quantity, this, null, 1f); foreach (var plantMetabolizable in PlantMetabolisms) { if (!plantMetabolizable.ShouldApply(args, random)) @@ -206,8 +209,10 @@ namespace Content.Shared.Chemistry.Reagent if (plantMetabolizable.ShouldLog) { var entity = args.TargetEntity; - entMan.System().Add(LogType.ReagentEffect, plantMetabolizable.LogImpact, - $"Plant metabolism effect {plantMetabolizable.GetType().Name:effect} of reagent {ID:reagent} applied on entity {entMan.ToPrettyString(entity):entity} at {entMan.GetComponent(entity).Coordinates:coordinates}"); + logger.Add( + LogType.ReagentEffect, + plantMetabolizable.LogImpact, + $"Plant metabolism effect {plantMetabolizable.GetType().Name:effect} of reagent {ID} applied on entity {entity}"); } plantMetabolizable.Effect(args); diff --git a/Content.Shared/Interaction/SharedInteractionSystem.cs b/Content.Shared/Interaction/SharedInteractionSystem.cs index 68e9d8a671..d29c3436ac 100644 --- a/Content.Shared/Interaction/SharedInteractionSystem.cs +++ b/Content.Shared/Interaction/SharedInteractionSystem.cs @@ -515,7 +515,7 @@ namespace Content.Shared.Interaction // all interactions should only happen when in range / unobstructed, so no range check is needed var message = new InteractHandEvent(user, target); RaiseLocalEvent(target, message, true); - _adminLogger.Add(LogType.InteractHand, LogImpact.Low, $"{ToPrettyString(user):user} interacted with {ToPrettyString(target):target}"); + _adminLogger.Add(LogType.InteractHand, LogImpact.Low, $"{user} interacted with {target}"); DoContactInteraction(user, target, message); if (message.Handled) return; diff --git a/Content.Shared/Mind/SharedMindSystem.cs b/Content.Shared/Mind/SharedMindSystem.cs index 8906e73248..309f37be3f 100644 --- a/Content.Shared/Mind/SharedMindSystem.cs +++ b/Content.Shared/Mind/SharedMindSystem.cs @@ -608,15 +608,14 @@ public abstract partial class SharedMindSystem : EntitySystem } /// - /// A string to represent the mind for logging + /// A string to represent the mind for logging. /// - public string MindOwnerLoggingString(MindComponent mind) + public MindStringRepresentation MindOwnerLoggingString(MindComponent mind) { - if (mind.OwnedEntity != null) - return ToPrettyString(mind.OwnedEntity.Value); - if (mind.UserId != null) - return mind.UserId.Value.ToString(); - return "(originally " + mind.OriginalOwnerUserId + ")"; + return new MindStringRepresentation( + ToPrettyString(mind.OwnedEntity), + mind.UserId != null, + mind.UserId ?? mind.OriginalOwnerUserId); } public string? GetCharacterName(NetUserId userId) @@ -733,3 +732,16 @@ public record struct GetCharactedDeadIcEvent(bool? Dead); /// [ByRefEvent] public record struct GetCharacterUnrevivableIcEvent(bool? Unrevivable); + +public sealed record MindStringRepresentation(EntityStringRepresentation? OwnedEntity, bool PlayerPresent, NetUserId? Player) : IAdminLogsPlayerValue +{ + public override string ToString() + { + var str = OwnedEntity?.ToString() ?? "mind without entity"; + if (Player != null) + str += $" ({(PlayerPresent ? "" : "originally ")} {Player})"; + return str; + } + + IEnumerable IAdminLogsPlayerValue.Players => Player == null ? [] : [Player.Value]; +} diff --git a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs index 35fa501398..315f8d8115 100644 --- a/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs +++ b/Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs @@ -141,7 +141,7 @@ public abstract class SharedObjectivesSystem : EntitySystem if (ev.Progress != null) return ev.Progress; - Log.Error($"Objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind.Comp)} didn't set a progress value!"); + Log.Error($"Objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} didn't set a progress value!"); return null; }