Showing posts with label Code. Show all posts
Showing posts with label Code. Show all posts

November 25, 2016

Tips about the fallback in Sitecore

Tip number 1

Problem: The fallback is disabled by default in the item:save events.
Tip: You can use the following code:
using(new FallbackDisabler(FallbackStates.Default))
{  
   db.GetItem()
   ...
}

Tip number 2

Problem: The fallback is not active in the scheduled tasks.
Tip: In the FieldFallback_00.config, you need to add the site 'scheduler' in the 'sites' tag.

Tip number 3

Problem: When you use the Link database to retrieve an item, you have a ItemLink (the link between the 2 objects). If you do a .GetSourceItem() on this ItemLink, the fallback is not applied.
Tip: You need to retrieve this item with the standard method: db.GetItem(itemId).

Tip number 4

Problem: The ancestor fallback didn't work in the other languages.
Tip: To enable the ancestor fallback on a field, you need to select the template's field and check the EnableAncestorFallback checkbox. But, be careful because this checkbox is not shared. So two solutions here: check it in every languages or change this checkbox to shared.

November 10, 2016

Sitecore Utility - Sanitize Query

Recently i had an issue with the standard Sitecore queries on a bucket item: when an Sitecore item begin with a 0 the queries may return no results.
This is of course an issue with the items who are in a date based bucket. You may also have some unexpected results if you have some of the reserved keywords in the item name as "and", "or", "self", "children", "parent", "sibilings", ...
So, I have create a function to "sanitize" your queries before the Sitecore queries.

And an example of the SanitizeQuery: the SanitizeQuery function:
var myQuery = "/sitecore/content/TestSites/Test/09/28/15/56/*".SanitizeQuery();
var item = Sitecore.Context.ContentDatabase.SelectSingleItem(myQuery);

I would like to create a collection of utils function on github with all those kind of reusable functions. Like this you can easilly extract the functions you are interested in or the whole project.
You will find this repository here: https://github.com/VGBenjamin/Sitecore.Utility
If you have functions that you would like to share in this directory  you can of course propose it this could become a new collaborative repository.

January 15, 2016

Deep drive into the Sitecore Client Pipelines with SPEAK

This post is very complex. If you read it from a rss or anything else I really suggest to read it directly into my blog because I have create a slideshow who is suppose to be more readable :-)

This is the forth post

December 3, 2014

Add a custom section in the content editor

Here the procedure to add a custom section after the Quick Info section in the content editor. Like this:

  • Create a new class like this:
    using Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditor;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace SitecoreTestCustomizeContentEditor
    {
        public class TestSection
        {
            public void Process(RenderContentEditorArgs args)
            {
                args.EditorFormatter.RenderSectionBegin(args.Parent, "CustomInfo", "CustomInfo", "CustomInfo", "Applications/16x16/information.png", true, true);
                args.EditorFormatter.AddLiteralControl(args.Parent, "<div>test value</div>");
                args.EditorFormatter.RenderSectionEnd(args.Parent, true, true);
            }
        }
    }
  • Deploy this class in your bin folder
  • Add a pipeline into web.config in the renderContentEditor section like this
    <renderContentEditor>
     <processor type="SitecoreTestCustomizeContentEditor.TestSection, SitecoreTestCustomizeContentEditor" />
     <processor type="Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditor.RenderSkinedContentEditor, Sitecore.Client" />
     <processor type="Sitecore.Shell.Applications.ContentEditor.Pipelines.RenderContentEditor.RenderStandardContentEditor, Sitecore.Client" />  
    </renderContentEditor>

June 24, 2013

Detach a media from the code

Here is a very small usefull function to detach the media from the c# code in sitecore:
private void DetachMedia(MediaItem mediaItem) 
{ 
 MediaUri mediaUri = MediaUri.Parse(mediaItem); 
 Media media = MediaManager.GetMedia((mediaUri)); 
 media.ReleaseStream(); 
 Context.ClientPage.SendMessage(this, "item:refresh(type=attachment)"); 
}

March 26, 2013

Use and evaluate the Rule field type

In sitecore one of the field you can use is the "Rule" field. This field is very powerfull and you can easilly implmeent your own rules. Here is a very small tutorial on it.

First of all, on your template you should select the field of thype "Rule":

Now on your item you have an editor like this one:




And the following function return is a rule field is true:
public bool EvaluateRule(string fieldName, Item item) 
{
 var ruleContext = new RuleContext();
 foreach (Rule<RuleContext> rule in RuleFactory.GetRules<RuleContext>(new[] { item }, fieldName).Rules)
 {
     if (rule.Condition != null)
     {
  var stack = new RuleStack();
  rule.Condition.Evaluate(ruleContext, stack);

  if (ruleContext.IsAborted)
  {
      continue;
  }
  if ((stack.Count != 0) && ((bool)stack.Pop()))
  {
      return true;
  }
     }
 }
 
 return false;
}

Ajax lazy loading with Sitecore

For one of our project we need to call some sublayout in ajax. This allow you to do some pagination or lazy loading and let sitecore resolve the context.

To do that, we will create our own LayoutResolver to respond to our query with only the corresponding rendering.

Our call ajax wll look like:
var url = "http://myurl/myItem?UseAjax=true&amp;PresentationId=BACBFEE1-C0BB-4D8E-BA1A-D9F50CCA5B7F";

$.ajax({
 type: 'GET',
 url: url
}).done(function (data) {
 //Your code
});

We will also need a layout for the ajax queries (in the following code the MY_AJAX_LAYOUT_ID is the id of this layout) and this layout will only contain one placeholder (MY_AJAX_PLACEHOLDER in the code)

Now here is the custom layout resolver (look at the comments into the code):
using System;
using System.Linq;
using System.Web;
using Sitecore.Data.Items;
using Sitecore.Pipelines.HttpRequest;

namespace MyNamespace
{
    public class AjaxLayoutResolver : LayoutResolver
    {
        public override void Process(HttpRequestArgs args)
        {
            bool useAjax;
     //First of all let's check if we are in ajax mode or not if not don't continue
            var result = bool.TryParse(HttpContext.Current.Request.Params["UseAjax], out useAjax);
            if (!result || !useAjax)
            {
                return;
            }

     //The second parameter we need to pass is the PresentationId if not present the query if not valid -> don't continue
            var presentationId = HttpContext.Current.Request.Params["PresentationId"];
            if (string.IsNullOrEmpty(presentationId))
            {
                return;
            }

     //If the current item is null return
            if (Sitecore.Context.Item == null)
            {
                return;
            }

     //Let's resolve the sublayout
            try
            {
  //Get the list of rendering for the current item
                var renderings = Sitecore.Context.Item.Visualization.GetRenderings(Sitecore.Context.Device, false);
  //If found
                if (renderings != null && renderings.Any())
                {
      //Get the first rendering corresponding to the requested one
                    var rendering = renderings.First(sublayout => sublayout.RenderingID.ToString().Equals(presentationId));
                    if (rendering != null)
                    {             
                        //Put this rendering into ajax layout
                        rendering.Placeholder = MY_AJAX_PLACEHOLDER;
                        var layoutItem = Sitecore.Context.Database.GetItem(MY_AJAX_LAYOUT_ID);
                        if (layoutItem != null)
                        {
                            var layout = new LayoutItem(layoutItem);
                            Sitecore.Context.Page.FilePath = layout.FilePath;
                            Sitecore.Context.Page.ClearRenderings();
                            Sitecore.Context.Page.AddRendering(rendering);
                        }
                    }
                }
            }
            catch (Exception exception)
            {
                Log.Warn("Cannot render this!", this, exception);
            }
        }
    }
}
And of could you need to include this resolver after the existing one by adding this into a .config file in \App_Config\Include\
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>          
          <processor patch:after="*[@type='Sitecore.Pipelines.HttpRequest.LayoutResolver, Sitecore.Kernel']" type="MyNamespace.AjaxLayoutResolver, MyNamespace"/>          
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

November 29, 2012

Use some asp controls into a XAML control

Here is a small tips to use the standard asp controls in a xaml application.

You just need to add this line into the xamlControls declaration:
xmlns:asp="http://www.sitecore.net/microsoft/webcontrols"

Here is a small example:

<xamlControls
  xmlns:x="http://www.sitecore.net/xaml"
  xmlns:ajax="http://www.sitecore.net/ajax"
  xmlns:rest="http://www.sitecore.net/rest"
  xmlns:r="http://www.sitecore.net/renderings"
  xmlns:xmlcontrol="http://www.sitecore.net/xmlcontrols"
  xmlns:p="http://schemas.sitecore.net/Visual-Studio-Intellisense"
  xmlns:asp="http://www.sitecore.net/microsoft/webcontrols"
  xmlns:html="http://www.sitecore.net/microsoft/htmlcontrols"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:wfm="http://www.sitecore.net/wfm/webcontrols">
   <MyNamespace.MyClass x:inherits="MyNamespace.MyClass, MyNamespace">
  <asp:TextBox ID="tMyTextBox" runat="server" Width="97%"/>
   </MyNamespace.MyClass>
</xamlControls>

November 2, 2012

Install a sitecore package in your code

Here is a code snippet to install a sitecore package into your code:
public void InstallPackage(string path)
{
    bool hasPostAction;
    string historyPath;

    // Use default logger
    ILog log = LogManager.GetLogger("root");
    XmlConfigurator.Configure((XmlElement)ConfigurationManager.GetSection("log4net"));

    FileInfo pkgFile = new FileInfo(path);

    if (!pkgFile.Exists)
        throw new ClientAlertException(string.Format("Cannot access path '{0}'. Please check path setting.", path));

    Sitecore.Context.SetActiveSite("shell");
    using (new SecurityDisabler())
    {
        using (new ProxyDisabler())
        {
            using (new SyncOperationContext())
            {
                Sitecore.Install.Framework.IProcessingContext context = new Sitecore.Install.Framework.SimpleProcessingContext(); // 
                Sitecore.Install.Items.IItemInstallerEvents events =
                    new Sitecore.Install.Items.DefaultItemInstallerEvents(new Sitecore.Install.Utils.BehaviourOptions(Sitecore.Install.Utils.InstallMode.Overwrite, Sitecore.Install.Utils.MergeMode.Undefined));
                context.AddAspect(events);
                Sitecore.Install.Files.IFileInstallerEvents events1 = new Sitecore.Install.Files.DefaultFileInstallerEvents(true);
                context.AddAspect(events1);
                var inst = new Sitecore.Install.Installer();
                inst.InstallPackage(Sitecore.MainUtil.MapPath(path), context);
            }
        }
    }            
}

October 9, 2012

Create your own pipeline

As you know sitecore use a lot of pipeline but did you already try to create your own pipeline for your custom actions?
To create your own pipeline in sitecore it is easy, you will need to:
  • Add your pipeline and porocessors in the web.config (or externalize it in the \app_config\include folder)
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
        <pipelines>
          <myCustomPipeline>        
            <processor type="MyNamespace.MyProcessor, MyDll" />          
          </myCustomPipeline>
        </pipelines>
      </sitecore>
    </configuration>
  • After you may call it with this code and t will wall all the processors in this pipeline:
    PipelineArgs args = new PipelineArgs();
    CorePipeline.Run("myCustomPipeline", args);
Simple doesn’t it? You may also use your own argument if you need to pass some additional properties to the processors. To do that, you just need to create a new class who inherit from PipelineArgs.

August 13, 2012

Use the sitecore webservice

Recently, I have used the sitecore webservices for the first time to run some jobs without the sitecore context.
If you plan to use it, you will need to choose between the sitecore webservice or the sitecore rocks webservice.

  • The sitecore webservice
    • + Included in sitecore by default
    • + Secured (you need to pass some credentials to execute the methods) 
    • Very basic 
  • The sitecore rocks webservice
    • + Include all feature from the basic one and add some functionalities
    • + Secured (you need to pass some credentials to execute the methods)
    • Need to add the webservice before using in (not by default in sitecore but it is only 2-3 files to copy)
To use the webservice you need to:
  1. Create a reference to your webservice into your project
  2. Create some credentials
    • Sitecore WS:
      OldWS.Credentials oldCredentials = new OldWS.Credentials();
      oldCredentials.UserName = @"sitecore\admin";
      oldCredentials.Password = "b";
    • Rocks WS:
      RocksWS.Credentials credentials = new RocksWS.Credentials();
      credentials.UserName = @"sitecore\admin";
      credentials.Password = "b";
  3. Create an instance of the webservice
    • Sitecore WS
      OldWS.VisualSitecoreServiceSoapClient oldWs = new OldWS.VisualSitecoreServiceSoapClient();
    • Rocks WS
      RocksWS.SitecoreWebService2SoapClient ws = new RocksWS.SitecoreWebService2SoapClient();
  4. Use the methods of the webservice for example
    • GetChildren:
      • Sitecore WS
        //The parent item
        const string dictionaryId = "{504AE189-9F36-4C62-9767-66D73D6C3084}";
        var dictionaryChildren = oldWs.GetChildren(dictionaryId, "master", OldCredentials);
      • Rocks WS
        //The parent item
        const string dictionaryId = "{504AE189-9F36-4C62-9767-66D73D6C3084}";
        var dictionaryChildren = Ws.GetChildren(dictionaryId, "master", Credentials);
Here is the official sitecore documentation: http://sdn.sitecore.net/Reference/Sitecore%206/Sitecore%20Web%20Service%20Reference.aspx

July 16, 2012

Create a custom remote event in sitecore - Demo

I have multiple comments on the post about the custom remote events, so I will give you a more complete example. If you need more information about how those events work please refer to the previous post.

You can download the full exaple as a zip file here.

In this example, I have also added a parameter in the event.

  • The event:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.Serialization;
    
    namespace BOL.Events
    {
        [DataContract]
        public class ClearCacheEvent
        {
            public ClearCacheEvent(string cacheName)
            {
                this.CacheName = cacheName;
            }
    
            // Properties
            [DataMember]
            public string CacheName { get; protected set; }
    
        }
    }
  • The event argument with the parameter:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sitecore.Events;
    
    namespace BOL.Events
    {
        [Serializable]
        public class ClearCacheEventArgs : EventArgs, IPassNativeEventArgs
        {
            public string CacheName { get; set; }
    
            public ClearCacheEventArgs(string cacheName)
            {
                this.CacheName = cacheName;
            }
        }
    }
    
  • The event handler:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sitecore.Diagnostics;
    using Sitecore.Events;
    
    namespace BOL.Events
    {
        public class ClearCacheEventHandler
        {
            /// 
            /// This methos is used to raise the local event
            /// 
            /// 
            public static void Run(ClearCacheEvent @event)
            {
                Log.Info("ClearCacheEventHandler - Run", typeof(ClearCacheEventHandler));
                ClearCacheEventArgs args = new ClearCacheEventArgs(@event.CacheName);
                Event.RaiseEvent("clearcache:remote", new object[] { args });
            }
        }
    }
  • The hook to register the event:
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sitecore.Events.Hooks;
    
    namespace BOL.Events
    {
        public class ClearCacheHook : IHook
        {
            public void Initialize()
            {
                Sitecore.Eventing.EventManager.Subscribe(new Action { ClearCacheEventHandler.Run });
            }
        }
    }
    
  • The code to trigger the event:
    public class TriggerEvent
    {
     public void Trigger()
     {
      ClearCacheEvent inst = new ClearCacheEvent("Navigation");
      Sitecore.Eventing.EventManager.QueueEvent(inst);
     }
    }
  • The config file to register the event:
    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
     <hooks>  
      <hook type="BOL.Events.ClearCacheHook, BOL"/>  
     </hooks> 
     
     <events>
      <event name="clearcache:remote">
       <handler type="Shurgard.Events.ClearStoreCache, Shurgard.Events" method="OnRemoteClearStoreCache"/>          
      </event>
     <events>
     
      </sitecore>
    </configuration>

October 18, 2011

Retreive the content editor language and item version

Here is a small tip to retreive the language and the version of the item selected in the sitecore content editor when you create a custom field for example.

public class MyCustomField : CustomDropListField
{
 public string ItemLanguage { get; set; }
 public string ItemVersion { get; set; }  
} 

Thank you to Mister Tony Locatelli from LBi Belgium for this tip :)

October 13, 2011

Crash IIS event 5011 and how to add your unhandled exceptions in the event viewer

Recently I had a bug in one of my website because of a recursive infinite loop. The effect was some random restart of IIS but noting in the sitecore logs and not way to know why.

In the windows event viewer I saw in the IIS section that the I had some IIS crashes with the event code 5011.

After googling a little bit I found this website: http://martinnormark.com/how-to-handle-iis-event-id-1009-event-id-5011-on-iis-7

So, if you create a dll with this class

#region Using

using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Web;

#endregion

/// <summary>
/// Handles all unhandled exceptions from in the current AppDomain. 
/// 
/// Works great to catch unhandled exceptions thrown by IIS's child threads, /// which will make the application pool terminate unexpectedly 

/// without logging. This makes sure your Exception /// is logged to the Application event log.
/// </summary>
public class UnhandledExceptionModule : IHttpModule
{
  #region Fields

  private static int _UnhandledExceptionCount = 0;
  private static string _SourceName = null;
  private static object _InitLock = new object();
  private static bool _Initialized = false;

  #endregion

  #region IHttpModule members

  public void Init(HttpApplication app)
  {

    // Do this one time for each AppDomain.
    if (!_Initialized)
    {
      lock (_InitLock)
      {
        if (!_Initialized)
        {
          string webenginePath = Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "webengine.dll");

          if (!File.Exists(webenginePath))
          {
            throw new Exception(String.Format(CultureInfo.InvariantCulture,                 "Failed to locate webengine.dll at '{0}'.  This module requires .NET Framework 2.0.", webenginePath));
          }

          FileVersionInfo ver = FileVersionInfo.GetVersionInfo(webenginePath);
          _SourceName = string.Format(CultureInfo.InvariantCulture, "ASP.NET {0}.{1}.{2}.0", ver.FileMajorPart,                 ver.FileMinorPart, ver.FileBuildPart);

          if (!EventLog.SourceExists(_SourceName))
          {
            throw new Exception(String.Format(CultureInfo.InvariantCulture,                 "There is no EventLog source named '{0}'. This module requires .NET Framework 2.0.", _SourceName));
          }

          AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(OnUnhandledException);

          _Initialized = true;
        }
      }
    }
  }

  public void Dispose()
  {
  }

  #endregion

  #region UnhandledException event handler

  public void OnUnhandledException(object o, UnhandledExceptionEventArgs e)
  {
    // Let this occur one time for each AppDomain.
    if (Interlocked.Exchange(ref _UnhandledExceptionCount, 1) != 0)
      return;

    StringBuilder message = new StringBuilder("\r\n\r\nUnhandledException logged by UnhandledExceptionModule:\r\n\r\nappId=");

    string appId = (string)AppDomain.CurrentDomain.GetData(".appId");
    if (appId != null)
    {
      message.Append(appId);
    }

    Exception currentException = null;
    for (currentException = (Exception)e.ExceptionObject; currentException != null; currentException = currentException.InnerException)
    {
      message.AppendFormat("\r\n\r\ntype={0}\r\n\r\nmessage={1}\r\n\r\nstack=\r\n{2}\r\n\r\n",
                           currentException.GetType().FullName,
                           currentException.Message,
                           currentException.StackTrace);
    }

    EventLog Log = new EventLog();
    Log.Source = _SourceName;
    Log.WriteEntry(message.ToString(), EventLogEntryType.Error);
  }

  #endregion
}

Add add this
<httpmodules>            
    <add name="UnhandledExceptionModule" type="UnhandledExceptionModule"/>        
    .....
</httpmodules>

You will have more details about the cause of this error a StackOverflowException in my case.

It takes me a lot of time to find why because I didn't find how to know witch line or class cause the exception but it already help a lot :)

This class also adds all the unhandled error in the event viewer so it is also great to have it.

June 23, 2011

Create a custom remote event in sitecore

Recently, I had to clear some cache on the frontend server from my backend.
To do that I have implemented a custom remote event. I did not find some documentation on sdn so here is the procedure (thank you to the Sergey Kravchenko from the sitecore support).

  1. Create your custom remote event class.  Remote event class is a usual class inherited from System.Object and marked with a [DataContract] attribute.
     
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.Serialization;
    
    namespace XXX
    {
        [DataContract]
        public class ClearCacheEvent
        {
            public ClearCacheEvent()
            {
    
            }
        }
    }
    
  2. Create a custom event arguments class, to store the arguments of the event. It also, can be empty. Both Remote event class and event arguments class will be used to add event to the EventQueue.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sitecore.Events;
    
    namespace XXX
    {
        public class ClearCacheEventArgs : EventArgs, IPassNativeEventArgs
        {
            public ClearCacheEventArgs(ClearCacheEvent @event)
            {
                
            }
        }
    }
    
  3. Create your custom event handler
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sitecore.Diagnostics;
    using Sitecore.Events;
    
    namespace XXX
    {
        public class ClearCacheEventHandler
        {
            /// <summary>
            /// The methos is the method that you need to implement as you do normally
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            public virtual void OnClearCacheRemote(object sender, EventArgs e)
            {
                Log.Info("Tadaa", this);
            }
    
            /// <summary>
            /// This methos is used to raise the local event
            /// </summary>
            /// <param name="event"></param>
            public static void Run(ClearCacheEvent @event)
            {
                Log.Info("ClearCacheEventHandler - Run", typeof(ClearCacheEventHandler));
                ClearCacheEventArgs args = new ClearCacheEventArgs(@event);
                Event.RaiseEvent("clearcache:remote", new object[] { args });
            }
        }
    }
    
  4. Create a custom Hook to bind event from the EventQueue and the "local event" in the CD server web config. This hook will call the method you have added in the event handler ("Run" method of the ClearCacheEventHandler class in our case)
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Sitecore.Events.Hooks;
    
    namespace XXX
    {
        public class ClearCacheHook : IHook
        {
            public void Initialize()
            {
                Sitecore.Eventing.EventManager.Subscribe(new Action(ClearCacheEventHandler.Run));
            }
        }
    }
    
  5. Trigger the event in your code
    ClearCacheEvent inst = new ClearCacheEvent();
    Sitecore.Eventing.EventManager.QueueEvent<ClearCacheEvent>(inst);
    
  6. Configure the web.config (or add it in a separated file in the /include folder it is cleaner)

    The hook:
    <hooks>
        <hook type="XXX.ClearCacheHook, XXX"/>
    </hooks>
    
    The event:
    <events>
        <event name="clearcache:remote">
            <handler type="XXX.ClearCacheEventHandler, XXX" method="OnClearCacheRemote"/>
        </event>
    </events>
    

How does it work?
  1. Your code add an event in the   EventQueue.
  2. The EventQueue is checked by the special task:
    <eventQueue>
        <processingInterval>00:00:02</processingInterval>
    </eventQueue>
    
  3. The Hook call a method who raise the corresponding local event.
  4. Your local is triggered.

May 26, 2011

Write you messages in a separated file

It is not always easy with a sitecore "out of the box" to know witch messages come from your custom code and witch messages come from sitecore himself.
Here is a small tip to write you custom messages in a separated file:

First of all, we will prefix all our messages with a custom prefix "SH - " in my case:
Log.Info("SH - My message", typeof(MyObject));
Log.Error("SH - My message", typeof(MyObject));
...

And then to write it in a custom log file change in the web.config in the log4net section:
<appender name="MyFileAppender" type="log4net.Appender.SitecoreLogFileAppender, Sitecore.Logging">
    <file value="$(dataFolder)/logs/mylog.{date}.txt" />
 <filter type="log4net.Filter.StringMatchFilter">
    <regexToMatch value="^SH .*" />
    </filter>
    <filter type="log4net.Filter.DenyAllFilter" />
    <appendToFile value="true" />
    <layout type="log4net.Layout.PatternLayout">
    <conversionPattern value="%4t %d{ABSOLUTE} %-5p %m%n" />
    </layout>
</appender>
You will need to adapt the regex with your custom prefix of course.

And second point after the logFileAppender of sitecore add you custom one:
<root>
    <priority value="DEBUG"/>
    <appender-ref ref="LogFileAppender"/>
    <appender-ref ref="MyFileAppender" />
</root>

You have a lot of example of what you can do with log4net here: http://logging.apache.org/log4net/release/config-examples.html

EDIT: You have an alternative syntaxt and more info about the filters here https://sitecoreblog.blogspot.be/2017/03/log4net-add-stacktrace-in-logs.html

December 1, 2010

Don't use HostName

I discover that it is better to not use Sitecore.Context.Site.HostName but Sitecore.Context.Site.TargetHostName because the HostName property is ok while you one have only one Hostname per site in the web.config.

The TargetHostName take the property TargetHostName of the site if specified in the site tag of the web.config or the first hostname if this property is not specified.