public override ErrorLogEntry GetError(string id) { ErrorLogEntry entry; try { id = new Guid(id).ToString(); } catch (FormatException exception) { throw new ArgumentException(exception.Message, id, exception); } var blobContainer = GetBlobContainer(); var blobs = blobContainer.ListBlobs(); var blobItem = blobs.FirstOrDefault(b => b.Uri.ToString().EndsWith(id + ".xml")); if (blobItem == null) throw new FileNotFoundException(string.Format("Cannot locate error file for error with ID {0}.", id)); using (var stream = new MemoryStream()) { var blob = blobContainer.GetBlobReference(blobItem.Uri.ToString()); blob.DownloadToStream(stream); stream.Position = 0; using (var reader = new XmlTextReader(stream)) { Error error = ErrorXml.Decode(reader); entry = new ErrorLogEntry(this, id, error); } } return entry; }
/// <summary> /// Logs an error to the application memory. /// </summary> /// <remarks> /// If the log is full then the oldest error entry is removed. /// </remarks> public override string Log(Error error) { if (error == null) { throw new ArgumentNullException("error"); } // // Make a copy of the error to log since the source is mutable. // Assign a new GUID and create an entry for the error. // error = (Error)((ICloneable)error).Clone(); error.ApplicationName = this.ApplicationName; Guid newId = Guid.NewGuid(); ErrorLogEntry entry = new ErrorLogEntry(this, newId.ToString(), error); _lock.AcquireWriterLock(Timeout.Infinite); try { if (_entries == null) { _entries = new EntryCollection(_size); } _entries.Add(entry); } finally { _lock.ReleaseWriterLock(); } return(newId.ToString()); }
protected override void OnLoad(EventArgs e) { // // Retrieve the ID of the error to display and read it from // the store. // string errorId = this.Request.QueryString["id"] ?? string.Empty; if (errorId.Length == 0) { return; } _errorEntry = this.ErrorLog.GetError(errorId); // // Perhaps the error has been deleted from the store? Whatever // the reason, bail out silently. // if (_errorEntry == null) { Response.Status = HttpStatus.NotFound.ToString(); return; } // // Setup the title of the page. // this.PageTitle = string.Format("Error: {0} [{1}]", _errorEntry.Error.Type, _errorEntry.Id); base.OnLoad(e); }
/// <summary> /// Logs an error to the application memory. /// </summary> /// <remarks> /// If the log is full then the oldest error entry is removed. /// </remarks> public override string Log(Error error) { if (error == null) { throw new ArgumentNullException("error"); } // // Make a copy of the error to log since the source is mutable. // Assign a new GUID and create an entry for the error. // error = error.CloneObject(); error.ApplicationName = ApplicationName; var newId = Guid.NewGuid(); var entry = new ErrorLogEntry(this, newId.ToString(), error); _lock.EnterWriteLock(); try { var entries = _entries ?? (_entries = new EntryCollection(_size)); entries.Add(entry); } finally { _lock.ExitWriteLock(); } return(newId.ToString()); }
public ErrorLoggedEventArgs(ErrorLogEntry entry) { if (entry == null) throw new ArgumentNullException("entry"); _entry = entry; }
protected override void OnLoad(EventArgs e) { // // Retrieve the ID of the error to display and read it from // the store. // string errorId = Mask.NullString(this.Request.QueryString["id"]); if (errorId.Length == 0) return; _errorEntry = this.ErrorLog.GetError(errorId); // // Perhaps the error has been deleted from the store? Whatever // the reason, bail out silently. // if (_errorEntry == null) { Response.Status = HttpStatus.NotFound.ToString(); return; } // // Setup the title of the page. // this.PageTitle = string.Format("Error: {0} [{1}]", _errorEntry.Error.Type, _errorEntry.Id); base.OnLoad(e); }
/// <summary> /// Logs an exception and its context to the error log. /// </summary> protected virtual void LogException(Exception e, HttpContextBase context) { if (e == null) { throw new ArgumentNullException("e"); } // // Fire an event to check if listeners want to filter out // logging of the uncaught exception. // ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, context); OnFiltering(args); if (args.Dismissed) { return; } // // Log away... // ErrorLogEntry entry = null; try { Error error = new Error(e, context); ErrorLog log = GetErrorLog(context); error.ApplicationName = log.ApplicationName; OnLogging(new ErrorLoggingEventArgs(error)); string id = log.Log(error); entry = new ErrorLogEntry(log, id, error); } catch (Exception localException) { // // IMPORTANT! We swallow any exception raised during the // logging and send them out to the trace . The idea // here is that logging of exceptions by itself should not // be critical to the overall operation of the application. // The bad thing is that we catch ANY kind of exception, // even system ones and potentially let them slip by. // Trace.WriteLine(localException); } if (entry != null) { OnLogged(new ErrorLoggedEventArgs(entry)); } }
public ErrorLoggedEventArgs(ErrorLogEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } _entry = entry; }
public void ProcessRequest(HttpContext context) { HttpResponse response = context.Response; response.ContentType = "application/xml"; // // Retrieve the ID of the requested error and read it from // the store. // string errorId = Mask.NullString(context.Request.QueryString["id"]); if (errorId.Length == 0) { throw new ApplicationException("Missing error identifier specification."); } ErrorLogEntry entry = ErrorLog.GetDefault(context).GetError(errorId); // // Perhaps the error has been deleted from the store? Whatever // the reason, pretend it does not exist. // if (entry == null) { throw new HttpException((int)HttpStatusCode.NotFound, string.Format("Error with ID '{0}' not found.", errorId)); } // // Stream out the error as formatted XML. // #if !NET_1_0 && !NET_1_1 XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = true; settings.NewLineOnAttributes = true; settings.CheckCharacters = false; XmlWriter writer = XmlWriter.Create(response.Output, settings); #else XmlTextWriter writer = new XmlTextWriter(response.Output); writer.Formatting = Formatting.Indented; #endif writer.WriteStartDocument(); writer.WriteStartElement("error"); ErrorXml.Encode(entry.Error, writer); writer.WriteEndElement(/* error */); writer.WriteEndDocument(); writer.Flush(); }
public override void Entries(IList <ErrorLogEntry> entries, int index, int count, int total) { Debug.Assert(entries != null); Debug.Assert(index >= 0); Debug.Assert(index + count <= entries.Count); if (count == 0) { return; } // // Setup to emit CSV records. // StringWriter writer = new StringWriter(); writer.NewLine = "\r\n"; CsvWriter csv = new CsvWriter(writer); CultureInfo culture = CultureInfo.InvariantCulture; DateTime epoch = new DateTime(1970, 1, 1); // // For each error, emit a CSV record. // for (int i = index; i < count; i++) { ErrorLogEntry entry = entries[i]; Error error = entry.Error; DateTime time = error.Time.ToUniversalTime(); string query = "?id=" + HttpUtility.UrlEncode(entry.Id); Uri requestUrl = Context.Request.Url; csv.Field(error.ApplicationName) .Field(error.HostName) .Field(time.ToString("yyyy-MM-dd HH:mm:ss", culture)) .Field(time.Subtract(epoch).TotalSeconds.ToString("0.0000", culture)) .Field(error.Type) .Field(error.Source) .Field(error.User) .Field(error.StatusCode.ToString(culture)) .Field(error.Message) .Field(new Uri(requestUrl, "detail" + query).ToString()) .Field(new Uri(requestUrl, "xml" + query).ToString()) .Field(new Uri(requestUrl, "json" + query).ToString()) .Record(); } Context.Response.Output.Write(writer.ToString()); }
public void Add(ErrorLogEntry entry) { Debug.Assert(entry != null); Debug.AssertStringNotEmpty(entry.Id); Debug.Assert(this.Count <= _size); if (this.Count == _size) { BaseRemoveAt(0); } BaseAdd(entry.Id, entry); }
public override ErrorLogEntry GetError(string id) { // todo: proper async support var task = _errorRepository.GetErrorAsync(id); var errorRecord = task.Result; if (errorRecord == null) { return null; } var entry = new ErrorLogEntry(this, id, errorRecord.ToError()); return entry; }
public override ErrorLogEntry GetError(string id) { Logger.Trace(string.Format("GetError(id: {0})", id)); ErrorDocument document; using (var session = _documentStore.OpenSession()) { document = session.Load<ErrorDocument>(id); } var error = document.MapToError(); var logEntry = new ErrorLogEntry(this, id, error); return logEntry; }
public override ErrorLogEntry GetError(string id) { ErrorLogEntry result; ErrorDocument document; using (var session = _documentStore.OpenSession()) { document = session.Load <ErrorDocument>(id); } if (!string.IsNullOrEmpty(document.ErrorXml)) { result = new ErrorLogEntry(this, id, ErrorXml.DecodeString(document.ErrorXml)); } else { result = new ErrorLogEntry(this, id, document.Error); } return(result); }
private static void RenderError(HtmlTextWriter writer, ErrorLogEntry entry, Uri baseUrl) { Debug.Assert(writer != null); Debug.Assert(entry != null); Debug.Assert(baseUrl != null); Debug.Assert(baseUrl.IsAbsoluteUri); var error = entry.Error; writer.RenderBeginTag(HtmlTextWriterTag.Li); var errorType = ErrorDisplay.HumaneExceptionErrorType(error); if (errorType.Length > 0) { var abbreviated = errorType.Length < error.Type.Length; if (abbreviated) { writer.AddAttribute(HtmlTextWriterAttribute.Title, error.Type); writer.RenderBeginTag(HtmlTextWriterTag.Span); } HttpUtility.HtmlEncode(errorType, writer); if (abbreviated) { writer.RenderEndTag(/* span */); } writer.Write(": "); } writer.AddAttribute(HtmlTextWriterAttribute.Href, baseUrl + "detail?id=" + HttpUtility.UrlEncode(entry.Id)); writer.RenderBeginTag(HtmlTextWriterTag.A); HttpUtility.HtmlEncode(error.Message, writer); writer.RenderEndTag(/* a */); writer.RenderEndTag(/* li */); }
protected override void Render(HtmlTextWriter writer) { if (writer == null) { throw new ArgumentNullException("writer"); } // // Retrieve the ID of the error to display and read it from // the log. // string errorId = Mask.NullString(this.Request.QueryString["id"]); if (errorId.Length == 0) { return; } ErrorLogEntry errorEntry = this.ErrorLog.GetError(errorId); if (errorEntry == null) { // TODO: Send error response entity Response.Status = HttpStatus.NotFound.ToString(); return; } // // If we have a host (ASP.NET) formatted HTML message // for the error then just stream it out as our response. // if (errorEntry.Error.WebHostHtmlMessage.Length == 0) { return; } writer.Write(errorEntry.Error.WebHostHtmlMessage); }
private static void RenderError(TextWriter writer, ErrorLogEntry entry, Uri baseUrl) { Debug.Assert(writer != null); Debug.Assert(entry != null); Debug.Assert(baseUrl != null); Debug.Assert(baseUrl.IsAbsoluteUri); var error = entry.Error; writer.Write("<li>"); var errorType = ErrorDisplay.HumaneExceptionErrorType(error); if (errorType.Length > 0) { var abbreviated = errorType.Length < error.Type.Length; if (abbreviated) { writer.Write("<span title='{0}'>", Html.Encode(error.Type).ToHtmlString()); } writer.Write(Html.Encode(errorType).ToHtmlString()); if (abbreviated) { writer.Write("</span>"); } writer.Write(": "); } writer.Write("<a href='{0}'>", Html.Encode(baseUrl + "detail?id=" + Uri.EscapeDataString(entry.Id)).ToHtmlString()); writer.Write(Html.Encode(error.Message).ToHtmlString()); writer.Write("</a>"); writer.Write("</li>"); }
public void ProcessRequest(HttpContext context) { HttpResponse response = context.Response; response.ContentType = "application/json"; // // Retrieve the ID of the requested error and read it from // the store. // string errorId = Mask.NullString(context.Request.QueryString["id"]); if (errorId.Length == 0) { throw new ApplicationException("Missing error identifier specification."); } ErrorLogEntry entry = ErrorLog.GetDefault(context).GetError(errorId); // // Perhaps the error has been deleted from the store? Whatever // the reason, pretend it does not exist. // if (entry == null) { throw new HttpException((int)HttpStatusCode.NotFound, string.Format("Error with ID '{0}' not found.", errorId)); } // // Stream out the error as formatted JSON. // ErrorJson.Encode(entry.Error, response.Output); }
public override void Entries(IList <ErrorLogEntry> entries, int index, int count, int total) { Debug.Assert(entries != null); Debug.Assert(index >= 0); Debug.Assert(index + count <= entries.Count); StringWriter writer = new StringWriter(); writer.NewLine = "\n"; if (_wrapped) { writer.WriteLine("<script type='text/javascript' language='javascript'>"); writer.WriteLine("//<[!CDATA["); } writer.Write(_callback); writer.Write('('); JsonTextWriter json = new JsonTextWriter(writer); json.Object() .Member("total").Number(total) .Member("errors").Array(); Uri requestUrl = Context.Request.Url; for (int i = index; i < count; i++) { ErrorLogEntry entry = entries[i]; writer.WriteLine(); if (i == 0) { writer.Write(' '); } writer.Write(" "); string urlTemplate = new Uri(requestUrl, "{0}?id=" + HttpUtility.UrlEncode(entry.Id)).ToString(); json.Object(); ErrorJson.Encode(entry.Error, json); json.Member("hrefs") .Array() .Object() .Member("type").String("text/html") .Member("href").String(string.Format(urlTemplate, "detail")).Pop() .Object() .Member("type").String("aplication/json") .Member("href").String(string.Format(urlTemplate, "json")).Pop() .Object() .Member("type").String("application/xml") .Member("href").String(string.Format(urlTemplate, "xml")).Pop() .Pop() .Pop(); } json.Pop(); json.Pop(); if (count > 0) { writer.WriteLine(); } writer.WriteLine(");"); if (_wrapped) { writer.WriteLine("//]]>"); writer.WriteLine("</script>"); if (count == 0) { writer.WriteLine(@"</body></html>"); } } Context.Response.Output.Write(writer); }
/// <summary> /// Logs an exception and its context to the error log. /// </summary> protected virtual void LogException(Exception e, HttpContextBase context) { if (e == null) throw new ArgumentNullException("e"); // // Fire an event to check if listeners want to filter out // logging of the uncaught exception. // ExceptionFilterEventArgs args = new ExceptionFilterEventArgs(e, context); OnFiltering(args); if (args.Dismissed) return; // // Log away... // ErrorLogEntry entry = null; try { Error error = new Error(e, context); ErrorLog log = GetErrorLog(context); error.ApplicationName = log.ApplicationName; OnLogging(new ErrorLoggingEventArgs(error)); string id = log.Log(error); entry = new ErrorLogEntry(log, id, error); } catch (Exception localException) { // // IMPORTANT! We swallow any exception raised during the // logging and send them out to the trace . The idea // here is that logging of exceptions by itself should not // be critical to the overall operation of the application. // The bad thing is that we catch ANY kind of exception, // even system ones and potentially let them slip by. // Trace.WriteLine(localException); } if (entry != null) OnLogged(new ErrorLoggedEventArgs(entry)); }
/// <summary> /// Logs an error to the application memory. /// </summary> /// <remarks> /// If the log is full then the oldest error entry is removed. /// </remarks> public override string Log(Error error) { if (error == null) throw new ArgumentNullException("error"); // // Make a copy of the error to log since the source is mutable. // Assign a new GUID and create an entry for the error. // error = (Error) ((ICloneable) error).Clone(); error.ApplicationName = this.ApplicationName; Guid newId = Guid.NewGuid(); ErrorLogEntry entry = new ErrorLogEntry(this, newId.ToString(), error); _lock.AcquireWriterLock(Timeout.Infinite); try { if (_entries == null) { _entries = new EntryCollection(_size); } _entries.Add(entry); } finally { _lock.ReleaseWriterLock(); } return newId.ToString(); }
/// <summary> /// Returns a page of errors from the application memory in /// descending order of logged time. /// </summary> public override int GetErrors(int pageIndex, int pageSize, IList<ErrorLogEntry> errorEntryList) { if (pageIndex < 0) throw new ArgumentOutOfRangeException("pageIndex", pageIndex, null); if (pageSize < 0) throw new ArgumentOutOfRangeException("pageSize", pageSize, null); // // To minimize the time for which we hold the lock, we'll first // grab just references to the entries we need to return. Later, // we'll make copies and return those to the caller. Since Error // is mutable, we don't want to return direct references to our // internal versions since someone could change their state. // ErrorLogEntry[] selectedEntries; int totalCount; _lock.AcquireReaderLock(Timeout.Infinite); try { if (_entries == null) return 0; int lastIndex = Math.Max(0, _entries.Count - (pageIndex * pageSize)) - 1; selectedEntries = new ErrorLogEntry[lastIndex + 1]; int sourceIndex = lastIndex; int targetIndex = 0; while (sourceIndex >= 0) { selectedEntries[targetIndex++] = _entries[sourceIndex--]; } totalCount = _entries.Count; } finally { _lock.ReleaseReaderLock(); } if (errorEntryList != null) { // // Return copies of fetched entries. If the Error class would // be immutable then this step wouldn't be necessary. // foreach (ErrorLogEntry entry in selectedEntries) { Error error = (Error)((ICloneable)entry.Error).Clone(); errorEntryList.Add(new ErrorLogEntry(this, entry.Id, error)); } } return totalCount; }
public override ErrorLogEntry GetError(string id) { ErrorLogEntry result; ErrorDocument document; using (var session = _documentStore.OpenSession()) { document = session.Load<ErrorDocument>(id); } if (!string.IsNullOrEmpty(document.AllXml)) { result = new ErrorLogEntry(this, id, ErrorXml.DecodeString(document.AllXml)); } else { result = new ErrorLogEntry(this, id, document.Error); } return result; }
public void WhenGetErrorIdIsCalled() { _result = _given.ErrorLog.GetError("dummy id"); }
public void ProcessRequest(HttpContext context) { context.Response.ContentType = "application/xml"; // // Get the last set of errors for this application. // const int pageSize = 15; List <ErrorLogEntry> errorEntryList = new List <ErrorLogEntry>(pageSize); ErrorLog log = ErrorLog.GetDefault(context); log.GetErrors(0, pageSize, errorEntryList); // // We'll be emitting RSS vesion 0.91. // RichSiteSummary rss = new RichSiteSummary(); rss.version = "0.91"; // // Set up the RSS channel. // Channel channel = new Channel(); string hostName = Environment.TryGetMachineName(context); channel.title = "Error log of " + log.ApplicationName + (hostName.Length > 0 ? " on " + hostName : null); channel.description = "Log of recent errors"; channel.language = "en"; channel.link = context.Request.Url.GetLeftPart(UriPartial.Authority) + context.Request.ServerVariables["URL"]; rss.channel = channel; // // For each error, build a simple channel item. Only the title, // description, link and pubDate fields are populated. // channel.item = new Item[errorEntryList.Count]; for (int index = 0; index < errorEntryList.Count; index++) { ErrorLogEntry errorEntry = (ErrorLogEntry)errorEntryList[index]; Error error = errorEntry.Error; Item item = new Item(); item.title = error.Message; item.description = "An error of type " + error.Type + " occurred. " + error.Message; item.link = channel.link + "/detail?id=" + HttpUtility.UrlEncode(errorEntry.Id); item.pubDate = error.Time.ToUniversalTime().ToString("r"); channel.item[index] = item; } // // Stream out the RSS XML. // context.Response.Write(XmlText.StripIllegalXmlCharacters(XmlSerializer.Serialize(rss))); }
public override ErrorLogEntry GetError(string id) { ErrorDocument document; using (var session = _documentStore.OpenSession(ApplicationName)) { document = session.Load<ErrorDocument>(id); } var result = new ErrorLogEntry(this, id, document.Error); return result; }
/// <summary> /// Returns a page of errors from the application memory in /// descending order of logged time. /// </summary> public override int GetErrors(int pageIndex, int pageSize, IList errorEntryList) { if (pageIndex < 0) { throw new ArgumentOutOfRangeException("pageIndex", pageIndex, null); } if (pageSize < 0) { throw new ArgumentOutOfRangeException("pageSize", pageSize, null); } // // To minimize the time for which we hold the lock, we'll first // grab just references to the entries we need to return. Later, // we'll make copies and return those to the caller. Since Error // is mutable, we don't want to return direct references to our // internal versions since someone could change their state. // ErrorLogEntry[] selectedEntries = null; int totalCount; _lock.AcquireReaderLock(Timeout.Infinite); try { if (_entries == null) { return(0); } totalCount = _entries.Count; int startIndex = pageIndex * pageSize; int endIndex = Math.Min(startIndex + pageSize, totalCount); int count = Math.Max(0, endIndex - startIndex); if (count > 0) { selectedEntries = new ErrorLogEntry[count]; int sourceIndex = endIndex; int targetIndex = 0; while (sourceIndex > startIndex) { selectedEntries[targetIndex++] = _entries[--sourceIndex]; } } } finally { _lock.ReleaseReaderLock(); } if (errorEntryList != null && selectedEntries != null) { // // Return copies of fetched entries. If the Error class would // be immutable then this step wouldn't be necessary. // foreach (ErrorLogEntry entry in selectedEntries) { Error error = (Error)((ICloneable)entry.Error).Clone(); errorEntryList.Add(new ErrorLogEntry(this, entry.Id, error)); } } return(totalCount); }
private void RenderError(HtmlTextWriter writer, ErrorLogEntry entry, Uri baseUrl) { Debug.Assert(writer != null); Debug.Assert(baseUrl != null); Debug.Assert(entry != null); Error error = entry.Error; writer.RenderBeginTag(HtmlTextWriterTag.Li); string errorType = ErrorDisplay.HumaneExceptionErrorType(error); if (errorType.Length > 0) { bool abbreviated = errorType.Length < error.Type.Length; if (abbreviated) { writer.AddAttribute(HtmlTextWriterAttribute.Title, error.Type); writer.RenderBeginTag(HtmlTextWriterTag.Span); } Server.HtmlEncode(errorType, writer); if (abbreviated) writer.RenderEndTag(/* span */); writer.Write(": "); } writer.AddAttribute(HtmlTextWriterAttribute.Href, baseUrl + "/detail?id=" + HttpUtility.UrlEncode(entry.Id)); writer.RenderBeginTag(HtmlTextWriterTag.A); Server.HtmlEncode(error.Message, writer); writer.RenderEndTag(/* a */); writer.RenderEndTag( /* li */); }
private void RenderErrors(HtmlTextWriter writer) { Debug.Assert(writer != null); // // Create a table to display error information in each row. // Table table = new Table(); table.ID = "ErrorLog"; table.CellSpacing = 0; // // Create the table row for headings. // TableRow headRow = new TableRow(); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Host", "host-col")); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Code", "code-col")); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Type", "type-col")); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Error", "error-col")); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "User", "user-col")); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Date", "date-col")); headRow.Cells.Add(FormatCell(new TableHeaderCell(), "Time", "time-col")); table.Rows.Add(headRow); // // Generate a table body row for each error. // for (int errorIndex = 0; errorIndex < _errorEntryList.Count; errorIndex++) { ErrorLogEntry errorEntry = (ErrorLogEntry)_errorEntryList[errorIndex]; Error error = errorEntry.Error; TableRow bodyRow = new TableRow(); bodyRow.CssClass = errorIndex % 2 == 0 ? "even-row" : "odd-row"; // // Format host and status code cells. // bodyRow.Cells.Add(FormatCell(new TableCell(), error.HostName, "host-col")); bodyRow.Cells.Add(FormatCell(new TableCell(), error.StatusCode.ToString(), "code-col", Mask.NullString(HttpWorkerRequest.GetStatusDescription(error.StatusCode)))); bodyRow.Cells.Add(FormatCell(new TableCell(), ErrorDisplay.HumaneExceptionErrorType(error), "type-col", error.Type)); // // Format the message cell, which contains the message // text and a details link pointing to the page where // all error details can be viewed. // TableCell messageCell = new TableCell(); messageCell.CssClass = "error-col"; Label messageLabel = new Label(); messageLabel.Text = this.Server.HtmlEncode(error.Message); HyperLink detailsLink = new HyperLink(); detailsLink.NavigateUrl = BasePageName + "/detail?id=" + HttpUtility.UrlEncode(errorEntry.Id); detailsLink.Text = "Details…"; messageCell.Controls.Add(messageLabel); messageCell.Controls.Add(new LiteralControl(" ")); messageCell.Controls.Add(detailsLink); bodyRow.Cells.Add(messageCell); // // Format the user, date and time cells. // bodyRow.Cells.Add(FormatCell(new TableCell(), error.User, "user-col")); bodyRow.Cells.Add(FormatCell(new TableCell(), error.Time.ToShortDateString(), "date-col", error.Time.ToLongDateString())); bodyRow.Cells.Add(FormatCell(new TableCell(), error.Time.ToShortTimeString(), "time-col", error.Time.ToLongTimeString())); // // Finally, add the row to the table. // table.Rows.Add(bodyRow); } table.RenderControl(writer); }