Simple way to prevent duplicate logging of errors
ErrorObject newError = new ErrorObject(…);


This is one of the cases where most people will probably agree that you can use var here. Doing that doesn't lose any information (you can still see at a glance that newError is an ErrorObject) and it makes the code shorter and more DRY.



return retVal;


In general, you shouldn't use integer retVals. If you need to report errors, use exceptions. If you want to report success or failure, use a bool. If you want to report multiple possible results, use an enum. If you want to report some kind of count (though I don't see how would that make sense here), at least name your variable something like count, not retVal.



//Store 4 previous errors to compare against
private static Queue&lt;ErrorObject&gt; PreviousErrors = new Queue&lt;ErrorObject&gt;(4);


This sounds like a job for a simple reusable custom collection. Something like:

class BoundedQueue&lt;T&gt; : IEnumerable&lt;T&gt;
{
    private readonly int count;
    private readonly Queue&lt;T&gt; queue;

    public BoundedQueue(int count)
    {
        this.count = count;
        this.queue = new Queue&lt;T&gt;(count);
    }

    public void Enqueue(T item)
    {
        if (queue.Count == count)
            queue.Dequeue();

        queue.Enqueue(item);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public IEnumerator&lt;T&gt; GetEnumerator()
    {
        return queue.GetEnumerator();
    }
}




lock (PreviousErrorLock)


Since you want to lock access to the PreviousErrors object, you should use that in the lock, you don't need another object for this. (An object used just for locking makes sense when you logically want to do something like lock (this), but that's not the case here.)



foreach (ErrorObject previousError in PreviousErrors)
{
    if (previousError.AdditionalInfo == newError.AdditionalInfo &amp;&amp;
    Math.Abs((previousError.Time - newError.Time).TotalSeconds) &lt; 3 &amp;&amp;
    previousError.Exception.GetType() == newError.Exception.GetType() &amp;&amp;
    previousError.Exception.Message == newError.Exception.Message &amp;&amp;
    previousError.Exception.TargetSite == newError.Exception.TargetSite)
    {
        isDuplicate = true;
        break;//Exit the loop
    }
}


You should extract this equality testing code into a separate method, possibly as ErrorObject.Equals(). With that, this code would simplify to:

bool isDuplicate = PreviousErrors.Contains(newError);




break;//Exit the loop


You don't need this comment. Everyone knows what break does inside a loop.



Also, I find the queue logic suspicious: when you detect a duplicate, you add it to the queue anyway. So if the sequence of exceptions is something like 1, 2, 2, 2, 2, 1, then you're going to report 1 twice, even though it's among the 2 most recent error kinds.