Skip to content

Commit 3a0f239

Browse files
jasonleenaylorpapeh
authored andcommitted
Add minimal logging of UnitOfWork activity
* Add logger to IOC container when LCM_TransactionLogPath environment variable is defined. * Use logging to add breadcrumbs to trace UOW calls. * This is a first step to try and track down some intermittent failures in FieldWorks unit tests.
1 parent 2283e93 commit 3a0f239

File tree

4 files changed

+77
-12
lines changed

4 files changed

+77
-12
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.IO;
4+
using System.Text;
5+
6+
namespace SIL.LCModel
7+
{
8+
internal class FileTransactionLogger : ITransactionLogger, IDisposable
9+
{
10+
private object m_lock = new object();
11+
private FileStream m_stream;
12+
13+
internal FileTransactionLogger(string filePath)
14+
{
15+
m_stream = File.Open(filePath, FileMode.Append, FileAccess.Write, FileShare.Read);
16+
}
17+
18+
~FileTransactionLogger()
19+
{
20+
Dispose(false);
21+
}
22+
23+
public void AddBreadCrumb(string description)
24+
{
25+
lock (m_lock)
26+
{
27+
m_stream.WriteAsync(Encoding.UTF8.GetBytes((description + Environment.NewLine).ToCharArray()), 0,
28+
description.Length + 1);
29+
}
30+
}
31+
32+
protected virtual void Dispose(bool disposing)
33+
{
34+
Debug.WriteLineIf(!disposing, "****** Missing Dispose() call for " + GetType() + ". *******");
35+
lock (m_stream)
36+
{
37+
m_stream?.Flush();
38+
m_stream?.Dispose();
39+
}
40+
}
41+
42+
public void Dispose()
43+
{
44+
Dispose(true);
45+
GC.SuppressFinalize(this);
46+
}
47+
}
48+
}

src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Diagnostics;
8+
using System.IO;
89
using System.Runtime.InteropServices;
910
using CommonServiceLocator;
1011
using SIL.LCModel.Application;
@@ -16,6 +17,7 @@
1617
using SIL.LCModel.DomainServices.DataMigration;
1718
using SIL.LCModel.Infrastructure;
1819
using SIL.LCModel.Infrastructure.Impl;
20+
using SIL.Reporting;
1921
using StructureMap;
2022
using StructureMap.Pipeline;
2123

@@ -56,6 +58,13 @@ internal LcmServiceLocatorFactory(BackendProviderType backendProviderType, ILcmU
5658
/// ------------------------------------------------------------------------------------
5759
public IServiceProvider CreateServiceLocator()
5860
{
61+
ITransactionLogger logger = null;
62+
63+
var logPath = Environment.GetEnvironmentVariable("LCM_TransactionLogPath");
64+
if (!string.IsNullOrEmpty(logPath))
65+
{
66+
logger = new FileTransactionLogger(Path.Combine(logPath, $"lcm_transaction.{DateTime.Now.Ticks}.log"));
67+
}
5968
// NOTE: When creating an object through IServiceLocator.GetInstance the caller has
6069
// to call Dispose() on the newly created object, unless it's a singleton
6170
// (registered with LifecycleIs(new SingletonLifecycle())) in which case
@@ -108,12 +117,6 @@ public IServiceProvider CreateServiceLocator()
108117
.For<IdentityMap>()
109118
.LifecycleIs(new SingletonLifecycle())
110119
.Use<IdentityMap>();
111-
// No. This makes a second instance of IdentityMap,
112-
// which is probably not desirable.
113-
//registry
114-
// .For<ICmObjectIdFactory>()
115-
// .LifecycleIs(new SingletonLifecycle())
116-
// .Use<IdentityMap>();
117120
// Register IdentityMap's other interface.
118121
registry
119122
.For<ICmObjectIdFactory>()
@@ -184,7 +187,7 @@ public IServiceProvider CreateServiceLocator()
184187
registry
185188
.For<IUndoStackManager>()
186189
.Use(c => (IUndoStackManager)c.GetInstance<IUnitOfWorkService>());
187-
190+
registry.For<ITransactionLogger>().Use(() => logger);
188191
// Add generated factories.
189192
AddFactories(registry);
190193

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace SIL.LCModel
2+
{
3+
internal interface ITransactionLogger
4+
{
5+
void AddBreadCrumb(string description);
6+
}
7+
}

src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using SIL.LCModel.Core.Cellar;
1313
using SIL.LCModel.Core.KernelInterfaces;
1414
using SIL.LCModel.Utils;
15+
using SIL.Reporting;
1516
using Timer = System.Timers.Timer;
1617

1718
namespace SIL.LCModel.Infrastructure.Impl
@@ -26,7 +27,7 @@ namespace SIL.LCModel.Infrastructure.Impl
2627
/// via the 'PropChanged' method of the IVwNotifyChange interface.
2728
/// In this implementation, the LCM ISILDataAccess implementation farms out this
2829
/// notification function to this class.
29-
/// IVwNotifyChange implementations are not to use this notificatin mechanism
30+
/// IVwNotifyChange implementations are not to use this notification mechanism
3031
/// to make additional data changes. That is done using the second set of Mediator clients.
3132
///
3233
/// The second set of Mediator clients is between CmObjects.
@@ -35,7 +36,7 @@ namespace SIL.LCModel.Infrastructure.Impl
3536
/// of properties on other CmObjects.
3637
///
3738
/// With this twofold Mediator mechanism,
38-
/// the IVwNotifyChange system can stay with its purpose of refeshing UI display,
39+
/// the IVwNotifyChange system can stay with its purpose of refreshing UI display,
3940
/// while the second system can take care of side effect data changes.
4041
/// </summary>
4142
/// <remarks>
@@ -103,6 +104,7 @@ internal enum BusinessTransactionState
103104
private readonly IDataStorer m_dataStorer;
104105
private readonly IdentityMap m_identityMap;
105106
private readonly ILcmUI m_ui;
107+
private readonly ITransactionLogger m_logger;
106108
internal ICmObjectRepositoryInternal ObjectRepository
107109
{
108110
get;
@@ -151,7 +153,7 @@ internal BusinessTransactionState CurrentProcessingState {
151153
/// <summary>
152154
/// Constructor.
153155
/// </summary>
154-
internal UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmObjectRepositoryInternal objectRepository, ILcmUI ui)
156+
internal UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmObjectRepositoryInternal objectRepository, ILcmUI ui, ITransactionLogger logger)
155157
{
156158
if (dataStorer == null) throw new ArgumentNullException("dataStorer");
157159
if (identityMap == null) throw new ArgumentNullException("identityMap");
@@ -160,6 +162,7 @@ internal UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmO
160162

161163
m_dataStorer = dataStorer;
162164
m_identityMap = identityMap;
165+
m_logger = logger;
163166
ObjectRepository = objectRepository;
164167
m_ui = ui;
165168
CurrentProcessingState = BusinessTransactionState.ReadyForBeginTask;
@@ -216,6 +219,7 @@ private void Dispose(bool fDisposing)
216219
m_saveTimer.Dispose();
217220
}
218221
IsDisposed = true;
222+
m_logger?.AddBreadCrumb("UOW Disposed.");
219223
}
220224
#endregion
221225

@@ -247,7 +251,7 @@ void SaveOnIdle(object sender, ElapsedEventArgs e)
247251
// If it is less than 2s since the user did something don't save to smooth performance (unless the user has been busy as a beaver for more than 5 minutes)
248252
if (now - m_ui.LastActivityTime < TimeSpan.FromSeconds(2.0) && now < (m_lastSave + TimeSpan.FromMinutes(5)))
249253
return;
250-
254+
m_logger.AddBreadCrumb("Saving from SaveOnIdle");
251255
SaveInternal();
252256
}
253257
}
@@ -282,6 +286,7 @@ public void Save()
282286

283287
private void SaveInternal()
284288
{
289+
m_logger?.AddBreadCrumb("Attempting Save.");
285290
// don't allow reentrant calls.
286291
if (m_fInSaveInternal)
287292
return;
@@ -309,6 +314,7 @@ private void SaveInternal()
309314
if (stack.CanUndo())
310315
undoable = true;
311316
}
317+
m_logger?.AddBreadCrumb($"Raising save event: New:{newbies.Count} Changed:{dirtballs.Count} Deleted:{goners.Count}");
312318
RaiseSave(undoable);
313319
}
314320

@@ -324,6 +330,7 @@ private void SaveInternal()
324330
return; // Don't try to save the changes we just reverted!
325331
}
326332

333+
m_logger?.AddBreadCrumb("Committing");
327334
// let the BEP determine if a commit should occur or not
328335
if (!m_dataStorer.Commit(realNewbies, dirtballs, goners))
329336
{
@@ -520,7 +527,7 @@ internal void SendPropChangedNotifications(IEnumerable<ChangeInformation> change
520527
ChangeInformation[] changes = subscribers.Length == 0 ? changesEnum.Where(ci => ci.HasNotifier).ToArray() : changesEnum.ToArray();
521528
if (changes.Length == 0)
522529
return;
523-
530+
m_logger?.AddBreadCrumb($"Sending prop changed notifications. Changes:{changes.Length} Subscribers: {subscribers.Length}");
524531
m_ui.SynchronizeInvoke.Invoke(() =>
525532
{
526533
foreach (IVwNotifyChange sub in subscribers)

0 commit comments

Comments
 (0)