With some more digging, I was able to work out a solution using a custom action filter attribute and decorating the FindByID() and GetAll() methods within the Repository. I'm happy that I didn't need to touch the controller and it was only three lines in the repository: the two decorators and a using statement.
Since copious calls to GetAll() will wind up stuffing a lot of data into the Sitecore log, I included a settings configuration so we can turn on/off logging the response content. We can use SlowCheetah transforms to modify the setting per environment if we wanted. You get three settings from which to choose:
off = Nothing gets logged
succinct = Logs the request method and uri, as well as the response status code
verbose = Includes everything in succinct as well as logs the response content
Code below (abbreviated where necessary).
Controller
using Sitecore.Services.Core;
using Sitecore.Services.Infrastructure.Sitecore.Services;
using System.Web.Mvc;
using ExampleCo.NC.Web.Entity;
using ExampleCo.NC.Web.Repository;
using ExampleCo.NC.Web.Security;
namespace ExampleCo.NC.Web.Controllers
{
[ValidateAntiForgeryToken]
[ServicesController]
[BasicAuthenticationAttribute]
[ExampleCoDataAuthorize(Roles = "extranet\\ExampleCo NC Users")]
public class NCDataController : EntityService<NCDataItem>
{
public NCDataController(IRepository<NCDataItem> repository): base(repository)
{
}
public NCDataController(): this(new NCDataRepository())
{
}
}
}
Repository
namespace ExampleCo.NC.Web.Repository {
public class NCDataRepository : IRepository<NCDataItem> {
public NCDataRepository() : this(new SitecoreContext()) { }
public NCDataRepository(ISitecoreContext context) {
...
}
...
[LogApiEventsFilter]
public NCDataItem FindById(string id)
{
return FetchNCItem(id);
}
[LogApiEventsFilter]
public IQueryable<NCDataItem> GetAll()
{
return FetchNCItems().AsQueryable();
}
...
}
}
Action Filter Attribute
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using System;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace ExampleCo.NC.Web.Filters {
public class LogApiEventsFilter : ActionFilterAttribute {
public override void OnActionExecuting(HttpActionContext actionContext) {
if (!String.IsNullOrEmpty(Settings.GetSetting("ExampleCo.NC.Web.LogLevel")) &&
(Settings.GetSetting("ExampleCo.NC.Web.LogLevel").ToLower() == "succinct") || (Settings.GetSetting("ExampleCo.NC.Web.LogLevel").ToLower() == "verbose")) {
string message = string.Format(System.Globalization.CultureInfo.CurrentCulture,
"ExampleCo NC API received a {0} request at {1}", actionContext.Request.Method, actionContext.Request.RequestUri);
Log.Info(message, this);
}
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) {
base.OnActionExecuted(actionExecutedContext);
string message = "";
if (!String.IsNullOrEmpty(Settings.GetSetting("ExampleCo.NC.Web.LogLevel")) &&
(Settings.GetSetting("ExampleCo.NC.Web.LogLevel").ToLower() == "succinct") || (Settings.GetSetting("ExampleCo.NC.Web.LogLevel").ToLower() == "verbose")) {
message = string.Format(System.Globalization.CultureInfo.CurrentCulture,
"ExampleCo NC API responded with code {0}", actionExecutedContext.Response.StatusCode);
Log.Info(message, this);
}
if (!String.IsNullOrEmpty(Settings.GetSetting("ExampleCo.NC.Web.LogLevel")) &&
(Settings.GetSetting("ExampleCo.NC.Web.LogLevel").ToLower() == "verbose") && (null != actionExecutedContext.Response.Content)) {
message = string.Format(System.Globalization.CultureInfo.CurrentCulture,
"ExampleCo NC API responded with content: {0}", actionExecutedContext.Response.Content.ReadAsStringAsync().Result);
Log.Info(message, this);
}
}
}
}
Config File
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
<sitecore>
...
<settings>
<!-- EXAMPLECO NC WEB LOG LEVEL
Specifies what level of logging occurs for the API service.
Valid Values: off, succinct, verbose
Default: succinct
-->
<setting name="ExampleCo.NC.Web.LogLevel" value="succinct" />
</settings>
</sitecore>
</configuration>