I have installed Seq in my local Sitecore instance.
The portal displays the logs & errors, but I'm unable to figure out the following:
After an error is logged, it takes around 3 mins to show up in Seq. Can we configure it to display real-time.
I do not see the exception & stack trace as shown in snap. What needs to be configured to view them.
In the portal, I would like to show the "Message" and in it's collapsible details, I would like to have the stack trace. Is it possible.
In my Sitecore instance (the notepad in snap), you can see how the error is logged. But in Seq portal it only says "STRATUM_ERROR".
I would like it to display "STARTUM_ERROR Input string was not in a correct format".
And the stack trace in its collapsible table.
So, I added nuget for Serilog.Exeptions and modified my class method like this:
protected override void SendBuffer(LoggingEvent[] events)
{
using (var log = new LoggerConfiguration()
.MinimumLevel.ControlledBy(new LoggingLevelSwitch(GetLogEventLevel()))
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
.WithDefaultDestructurers()
.WithRootName("Message").WithRootName("Exception").WithRootName("Source")
)
.Enrich.WithMachineName()
.Enrich.WithEnvironmentUserName()
.Enrich.WithProcessId()
.Enrich.WithProcessName()
.Enrich.WithProperty("ThreadId", SystemInfo.CurrentThreadId)
.Enrich.WithMemoryUsage()
.WriteTo.Seq(SeqHost, apiKey: ApiKey)
.CreateLogger())
{
foreach (var thisEvent in events)
{
LogEvent(log, thisEvent);
}
}
}
private void LogEvent(Logger log, LoggingEvent loggingEvent)
{
if (loggingEvent.Level == Level.ERROR)
{
message.AppendLine(loggingEvent.RenderedMessage);
message.AppendLine(loggingEvent.GetExceptionStrRep());
log.Error(message.ToString());
}
}
But now it shows like this:
Seems like the .Enrich.WithExceptionDetails has no effect.
Related
I want my code to be called everytime someone views or downloads anything in Document And Media:
View:
Download:
The content URLs of the view and download (to which the HTTP response is an actual preview image or PDF itself being transfered) are respectively:
http://localhost:8080/documents/20143/0/invoice_ABC_2017.10.27.pdf/c44fd479-331b-f393-7879-973c5cecf086?version=1.0&previewFileIndex=1
http://localhost:8080/documents/20143/0/invoice_ABC_2017.10.27.pdf/c44fd479-331b-f393-7879-973c5cecf086?download=true
The responses to both requests seems to be built by WebServerServlet.sendFile, a part of Liferay which is unfortunately not an OSGi module.
My first instinct would have been to implement ModelListener, but it only has methods for creation/update/deletion events, nothing for read events.
How to intercept these events in Liferay? (7 EE DXP)
Model listeners are connected to the CRUD operation that can happen on an entity.
You could attach your self to the download action. Have a look here https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/converting-strutsactionwrappers-to-mvccommands
Preview
Preview (in the sense of the preview page being displayed by any user) can be intercepted by deploying a component that takes the place of the MVCRenderCommand.class service. To do that, copy Liferay's ViewFileEntryMVCRenderCommand.java and add your code in the render method:
#Component(
property = {
"service.ranking:Integer=100",
"javax.portlet.name=" + DLPortletKeys.DOCUMENT_LIBRARY,
"javax.portlet.name=" + DLPortletKeys.DOCUMENT_LIBRARY_ADMIN,
"javax.portlet.name=" + DLPortletKeys.MEDIA_GALLERY_DISPLAY,
"mvc.command.name=/document_library/view_file_entry"
},
service = MVCRenderCommand.class
)
public class MyViewFileEntryMVCRenderCommand implements MVCRenderCommand {
#Override
public String render(
RenderRequest renderRequest, RenderResponse renderResponse)
throws PortletException {
DoMyAuditThing();
[...]
}
[...]
}
Download
Download (in the sense of a Document and Media being actually downloaded) can be intercepted by creating a Servlet Filter (copied from the Liferay plugin samples) with this liferay-hook.xml configuration:
(UPDATE: Just after writing this code I realized that there is now a better way to write Servlet Filters)
<hook>
<servlet-filter>
<servlet-filter-name>Sample Filter</servlet-filter-name>
<servlet-filter-impl>com.liferay.sampleservletfilter.hook.filter.SampleFilter</servlet-filter-impl>
<init-param>
<param-name>hello</param-name>
<param-value>world</param-value>
</init-param>
</servlet-filter>
<servlet-filter-mapping>
<servlet-filter-name>Sample Filter</servlet-filter-name>
<before-filter>SSO Open SSO Filter</before-filter>
<url-pattern>/documents/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</servlet-filter-mapping>
</hook>
Note the <url-pattern>/documents/*</url-pattern> part.
The Filter class:
public class SampleFilter implements Filter {
#Override
public void doFilter(
ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain)
throws IOException, ServletException {
String uri = (String)servletRequest.getAttribute(
WebKeys.INVOKER_FILTER_URI);
// Extract information
String[] tokens = uri.split("/");
if(tokens.length < 6) {
System.out.println("Failed to parse download URI (Too few slashes): " + uri);
filterChain.doFilter(servletRequest, servletResponse);
return;
}
long groupId;
try {
groupId = Long.parseLong(tokens[2]);
}
catch(NumberFormatException e) {
System.out.println("Failed to parse download URI (Group not a number): " + uri);
filterChain.doFilter(servletRequest, servletResponse);
return;
}
String uuid = tokens[5];
System.out.println("group:" + groupId + " uuid:" + uuid);
DLFileEntry fileEntry = DLFileEntryLocalServiceUtil.fetchDLFileEntryByUuidAndGroupId(uuid, groupId);
// Send it to your audit
[...]
filterChain.doFilter(servletRequest, servletResponse);
}
}
A problem is that it seems to also catch unnecessary events when showing the Document and Media page... I investigate.
You could implement the Service Wrapper and in particular the getFile method. This method it’s called when the user request the download of the file.
I've following code, this was copied from one of questions here on SOF,
private void showMyMessage() {
ApplicationManager.getApplication().invokeLater(() -> {
com.intellij.notification.Notification notification = GROUP_DISPLAY_ID_INFO
.createNotification("<html>TLogin failed", " Go to Settings to setup login data!</html>",
NotificationType.ERROR,
new NotificationListener.UrlOpeningListener(true));
Project[] projects = ProjectManager.getInstance().getOpenProjects();
Notifications.Bus.notify(notification, projects[0]);
});
}
I would like to have a link instead text "LINK!!!", what can you suggest ?
I think that I need to create action and add this action to my group GROUP_DISPLAY_ID_INFO, but this group is not in xml it's just in code exists.
If take my code above as an example, need to add right after new
NotificationListener.UrlOpeningListener(true))
addAction(new NotificationAction("Settings") {
#Override
public void actionPerformed (#NotNull AnActionEvent anActionEvent,
#NotNull Notification notification){
DataContext dataContext = anActionEvent.getDataContext();
Project project = PlatformDataKeys.PROJECT.getData(dataContext)
ShowSettingsUtil.getInstance().showSettingsDialog(project,
YOURCLASS.class);
}
Where yourclass.class is a class which implements Configurable interface
And now on click on Settings you will see opened settings window (yourclass.class)
private static void showMyMessage(String LINK) {
ApplicationManager.getApplication().invokeLater(() -> {
Notification notification = GROUP_DISPLAY_ID_INFO
.createNotification("<html>TLogin failed", " Go to Settings to setup login data!</html>",
NotificationType.ERROR,
new NotificationListener.UrlOpeningListener(true));
Project[] projects = ProjectManager.getInstance().getOpenProjects();
Notifications.Bus.notify(notification, projects[0]);
});
}
Just replace the link as a parameter, and use it like showMyMessage("http://google.com")
Also you don't need to config the group display id in xml, just write the id in code.
Layout mentioned in config file is as follows:
Timestamp: ${date}${newline}Title: ${event-properties:item=Title}${newline}Message: ${message}${newline}Machine: ${machinename}${newline}${newline}${LayoutFooter}
On exception, I want to add two more properties to this layout, which includes Stack Trace and Inner Exception Message.
I am achieving the above requirement, by modifying the layout pattern to:
Timestamp: ${date}${newline}Title: ${event-properties:item=Title}${newline}Message: ${message}${newline}${event-properties:item=StackTrace}${event-properties:item=InnerException}Machine: ${machinename}${newline}${newline}${LayoutFooter}
And then through code,
private static void WriteLog(LogEvent logEvent)
{
var log = LogManager.GetLogger(logEvent.Logger);
LogEventInfo logMsg = new LogEventInfo();
logMsg.Message = logEvent.Message;
logMsg.Level = logEvent.LogLevel;
logMsg.Properties.Add("Title", logEvent.Title);
if(!string.IsNullOrEmpty(logEvent.StackTrace))
{
logMsg.Properties.Add("StackTrace", "Stack Trace: " + logEvent.StackTrace + Environment.NewLine);
}
if(!string.IsNullOrEmpty(logEvent.InnerException))
{
logMsg.Properties.Add("InnerException", "Inner Exception: " + logEvent.InnerException + Environment.NewLine);
}
log.Log(logMsg);
}
By following above approach, if I need to add more extended properties, I need to change config file and code.
In case of single config file, this approach is fine, but in case of multiple config files, it is time consuming.
Is there any way, through which I can add extended properties only by changing code, and no change in config file.
I was able to achieve this functionality, when I was using Microsoft Enterprise Logging library, as it had ExtendedProperties property in LogEntry class of Microsoft.Practices.EnterpriseLibrary.Logging.
Is there any way, through which I can add extended properties only by changing code, and no change in config file.
There is a ${all-event-properties} renderer and it has multiple parameters how to render.
I have some custom logging in my plugin and want to include the contents of my tracingService in my custom logging (which is called within a catch block, before the plugin finishes).
I cant seem to access the content of tracingService. I wonder if it is accessible at all?
I tried tracingService.ToString() just incase the devs had provided a useful overload, alas as expected I get name of the class "Microsoft.Crm.Sandbox.SandboxTracingService".
Obviously Dynamics CRM makes use of the tracingService content towards the end of the pipeline if it needs to.
Anybody have any ideas on this?
Kind Regards,
Gary
The tracing service does not provide access to the trace text during execution but that can be overcome by creating your own implementation of ITracingService. Note, you cannot get any text that was written to the trace log prior to the Execute method of your plugin being called - meaning if you have multiple plugins firing you won't get their trace output in the plugin that throws the exception.
public class CrmTracing : ITracingService
{
ITracingService _tracingService;
StringBuilder _internalTrace;
public CrmTracing(ITracingService tracingService)
{
_tracingService = tracingService;
_internalTrace = new StringBuilder();
}
public void Trace(string format, params object[] args)
{
if (_tracingService != null) _tracingService.Trace(format, args);
_internalTrace.AppendFormat(format, args).AppendLine();
}
public string GetTraceBuffer()
{
return _internalTrace.ToString();
}
}
Just instantiate it in your plugin passing in the CRM provided ITracingService. Since it is the same interface it works the same if you pass it to other classes and methods.
public class MyPlugin : IPlugin
{
public void Execute(IServiceProvider serviceProvider)
{
var tracingService = new CrmTracing((ITracingService)serviceProvider.GetService(typeof(ITracingService)));
tracingService.Trace("Works same as always.");
var trace = tracingService.GetTraceBuffer();
}
}
To get the traceInfo string from traceService at runtime I used debugger to interrogate the tracingService contents.
So the trace string is accessible from these expressions...
for Plugins
((Microsoft.Crm.Extensibility.PipelineTracingService)(tracingService)).TraceInfo
for CWA
((Microsoft.Crm.Workflow.WorkflowTracingService)(tracingService)).TraceInfo
You can drill into the tracing service by debugging and extract the expression.
However, at design time neither of these expressions seem to be accessible from any of the standard CRM 2011 SDK dlls... so not sure if its possible as yet.
I want to add some custom code during the login function, in particular i want to redirect the user after login to the previous page.
For example: i'm on page A , i want to download something from this page, but i'm not authorized. Then pops a popup with link to the login page. After successful login i'm back on page A.
For this purpose i want to overwrite the LoginWidged and to set value to"this.DestinationPageUrl" dynamically.
I read about similar issues here and here, but there isn't an example how to overwrite this LoginWidget class.
I create CustomLoginControl.cs file in my project and register as a new custom control, but after rendering it on the page, it didn't work. Login button does not make nothing. I'm not sure what exactly have to do and which of methods have to overwrite.
namespace SitefinityWebApp.UserControls
{
public class CustomLoginControl : Telerik.Sitefinity.Web.UI.PublicControls.LoginWidget
{
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
this.DestinationPageUrl = "http://previousPage.com";
base.Render(writer);
}
}
}
Can you give me an example how to overwrite this class to work properly.
Version: Sitefinity 5.0, Claims-based authentication
I've done something similar but instead of overriding the login control you can subscribe and capture the UnauthorizedAccess event, send the user to your login page with your redirect page as a query string parameter. You'll need to add a Global.asax / Global.asax.cs file to your project, then add this to the Application_Start function:
protected void Application_Start(object sender, EventArgs e)
{
Bootstrapper.Initialized += BootstrapperInitialized;
}
Then add these two functions:
private void BootstrapperInitialized(object sender, ExecutedEventArgs e)
{
if (e.CommandName == "Bootstrapped")
{
EventHub.Subscribe<IUnauthorizedPageAccessEvent>(OnUnauthorizedAccess);
}
}
private void OnUnauthorizedAccess(IUnauthorizedPageAccessEvent unauthorizedEvent)
{
var manager = ConfigManager.GetManager();
string loginPage = manager.GetSection<ProjectConfig>().DefaultSite.FrontEndLoginPageUrl;
var redirectParam = unauthorizedEvent.RedirectUrl.Replace(string.Format("{0}?ReturnUrl=", loginPage), string.Empty);
var escaped = Uri.EscapeDataString(redirectParam);
unauthorizedEvent.HttpContext.Response.Redirect(string.Format("{0}?ReturnUrl={1}", loginPage, escaped));
}
You will also need to set your default front end login page in the settings under Administration -> Settings -> Advanced -> Project -> DefaultSite and the FrontEndLoginPageUrl setting.
This works for me on a 6.3 site, not sure if this is available in Sitefinity 5 or not.