lordmilko / PrtgAPI

C#/PowerShell interface for PRTG Network Monitor
MIT License
305 stars 38 forks source link

error getting probe properties #374

Open kbilsted opened 10 months ago

kbilsted commented 10 months ago

Describe the bug

using the api for the first time i can get probe information, but an exception is raised when getting properties

Steps to reproduce

using PrtgAPI;
using PrtgAPI.Parameters;

var user = "prtg-creator";
var password = "prtg";
var client = new PrtgClient("https://prtg.acme-corp.net/",user, password, AuthMode.Password,false);
Sensor[] sensors = client.QuerySensors(s=>s.Name.ToLower().Contains("myservice")).ToArray();

Console.WriteLine($"found {sensors.Length} sensors");
foreach (var sensor in sensors)
{
    Console.WriteLine($"{sensor.Name}\n active: {sensor.Active}\n type: {sensor.Type}\n tags: {string.Join(", ",sensor.Tags)}");
    var props =client.GetProbeProperties(sensor.Id);
    Console.WriteLine(props);
};

What is the output of 'Get-PrtgClient -Diagnostic'?

System.Xml.XmlException
  HResult=0x80131940
  Message='=' is an unexpected token. The expected token is ';'. Line 1, position 298.
  Source=System.Private.Xml
  StackTrace:
   at System.Xml.XmlTextReaderImpl.Throw(Exception e)
   at System.Xml.XmlTextReaderImpl.Throw(String res, String[] args)
   at System.Xml.XmlTextReaderImpl.ThrowUnexpectedToken(String expectedToken1, String expectedToken2)
   at System.Xml.XmlTextReaderImpl.ThrowUnexpectedToken(Int32 pos, String expectedToken1, String expectedToken2)
   at System.Xml.XmlTextReaderImpl.HandleEntityReference(Boolean isInAttributeValue, EntityExpandType expandType, Int32& charRefEndPos)
   at System.Xml.XmlTextReaderImpl.ParseText(Int32& startPos, Int32& endPos, Int32& outOrChars)
   at System.Xml.XmlTextReaderImpl.FinishPartialValue()
   at System.Xml.XmlTextReaderImpl.get_Value()
   at System.Xml.Linq.XContainer.ContentReader.ReadContentFrom(XContainer rootContainer, XmlReader r)
   at System.Xml.Linq.XContainer.ReadContentFrom(XmlReader r)
   at System.Xml.Linq.XContainer.ReadContentFrom(XmlReader r, LoadOptions o)
   at System.Xml.Linq.XDocument.Load(XmlReader reader, LoadOptions options)
   at PrtgAPI.Request.RequestEngine.ValidateHttpResponse(HttpResponseMessage responseMessage, PrtgResponse response)
   at PrtgAPI.Request.RequestEngine.ExecuteRequest(PrtgRequestMessage request, CancellationToken token, Func`2 responseParser)
   at PrtgAPI.PrtgClient.GetObjectProperties[T](Either`2 objectOrId, ObjectType objectType, ObjectProperty mandatoryProperty)
   at PrtgAPI.PrtgClient.GetProbeProperties(Either`2 probe)
   at Program.<Main>$(String[] args) in C:\git\tools\prtg creator\xx\Program.cs:line 17

cannot easily find the raw xml that it has error parsing

Additional context

No response

lordmilko commented 10 months ago

Hi @kbilsted,

Just prior to calling GetProbeProperties, can you follow the steps listed in Troubleshooting as follows

client.LogLevel = LogLevel.Request | LogLevel.Response;

client.LogVerbose (sender, args) => {
    Console.WriteLine(args.Message);
};

and then provide the resulting XML output. Please check there is no sensitive information in the output prior to posting

kbilsted commented 10 months ago

I get the following output

Synchronously executing request https://xxxx.net/controls/objectdata.htm?id=15362&objecttype=probenode&username=yyyyy&passhash=22222222
  <div class="errormsg"><p>PRTG Network Monitor has discovered a problem. Your last request could not be processed properly.</p>
  <h3>Error message: The selected object cannot be used here.</h3>
  <small style="padding:5px;text-align:left">
  URL: /controls/objectdata.htm<br>
  Parameters: id=15362&objecttype=probenode&username=yyyyy&passhash=***&</small></div>

not sure it really helps me much. The problem im trying to solve is to figure out what is the url used in my http probe and how can i change this url from your api-library. I figured since the information was not available in the Sensor I had to dig into other data structures after the information.

Not sure if it is relevant, but alternatively to logging, you can add data to exceptions by changing

throw new PrtgRequestException("PRTG was unable to complete the request. The server responded with the following error: " + str.EnsurePeriod());

to

var ex = new PrtgRequestException("PRTG was unable to complete the request. The server responded with the following error: " + str.EnsurePeriod());
ex.Data.Add("response", response);
throw ex;
lordmilko commented 10 months ago

Ah, I see the issue!

var props =client.GetProbeProperties(sensor.Id);

You are attempting to retrieve the probe properties of a sensor

To retrieve sensor properties you must use GetSensorProperties

Additionally, if the property you want isn't defined on the object emitted from GetSensorProperties, you can use GetObjectPropertiesRaw to view all properties that exist (albeit in a non type safe manor)

lordmilko commented 10 months ago

Also, I can't believe I never tested this before, but I don't think my IQueryable provider supports doing ToLower like this

Sensor[] sensors = client.QuerySensors(s=>s.Name.ToLower().Contains("myservice")).ToArray();

From a quick test I think this will result in pulling all sensors from the server, and then filtering client side. On this basis it might be best to retrieve sensors normally

//The Contains operator is case insensitive
client.GetSensors(Property.Name, FilterOperator.Contains, "myservice");
kbilsted commented 10 months ago

Ah, I see the issue!

var props =client.GetProbeProperties(sensor.Id);

You are attempting to retrieve the probe properties of a sensor

To retrieve sensor properties you must use GetSensorProperties

Additionally, if the property you want isn't defined on the object emitted from GetSensorProperties, you can use GetObjectPropertiesRaw to view all properties that exist (albeit in a non type safe manor)

OMG! What a silly mistake! Sorry for wasting your time on that. hmm.. oh!.. this was just a test to see the rigor of your error handling... :-)

I am not sure it makes sense, but why do we not first fetch the object and check the type to see that it is compatible with the operation we are doing? If it takes too much time, perhaps an extra parameter can be added turning this behaviour on/off. or maybe do it in some introduced exception handling.

kbilsted commented 10 months ago

Also, I can't believe I never tested this before, but I don't think my IQueryable provider supports doing ToLower like this

Sensor[] sensors = client.QuerySensors(s=>s.Name.ToLower().Contains("myservice")).ToArray();

From a quick test I think this will result in pulling all sensors from the server, and then filtering client side. On this basis it might be best to retrieve sensors normally

//The Contains operator is case insensitive
client.GetSensors(Property.Name, FilterOperator.Contains, "myservice");

Many thanks, i believe you are correct, the query takes a very long time!

Can the filter-operations you suggest be specified as case insensitive? We have a mix of manually created probes over the last decade, so you will find all sorts of mess in there.

lordmilko commented 10 months ago

If GetProbeProperties first had to do GetProbe on the ID you passed in to verify whether or not it's actually a probe, this would double the number of API requests that need to be performed for this one operation and decrease performance. If you pass the wrong type of object in, PRTG will spit out an error message (albeit one that doesn't explain the issue as PrtgAPI might)

<div class="errormsg"><p>PRTG Network Monitor has discovered a problem. Your last request could not be processed properly.</p>
<h3>Error message: The selected object cannot be used here.</h3>

In this case, this failed because there is a bug in PrtgAPI's parsing of this error message. If PrtgAPI had been able to parse this message properly, it would have been a lot easier to see the reason that your call to GetProbeProperties was failing

lordmilko commented 10 months ago

Can the filter-operations you suggest be specified as case insensitive? We have a mix of manually created probes over the last decade, so you will find all sorts of mess in there.

My experience is that when exactly one filter is specified, FilterOperator.Equals is case insensitive. However, if two filters are specified, PRTG treats Equals as case sensitive. This is not very reliable, so as a workaround you can use FilterOperator.Contains which is always treated as case insensitive. This is how PrtgAPI's PowerShell cmdlets handle applying filters. The downside to this however is that if you're after myservice (case insensitive), a Contains filter will also give you myservice1

kbilsted commented 10 months ago

If GetProbeProperties first had to do GetProbe on the ID you passed in to verify whether or not it's actually a probe, this would double the number of API requests that need to be performed for this one operation and decrease performance. If you pass the wrong type of object in, PRTG will spit out an error message (albeit one that doesn't explain the issue as PrtgAPI might)

<div class="errormsg"><p>PRTG Network Monitor has discovered a problem. Your last request could not be processed properly.</p>
<h3>Error message: The selected object cannot be used here.</h3>

In this case, this failed because there is a bug in PrtgAPI's parsing of this error message. If PrtgAPI had been able to parse this message properly, it would have been a lot easier to see the reason that your call to GetProbeProperties was failing

Hi Milko

Yes the tradeof is twice the number of requests. Perhaps that is a very low overhead? Perhaps it is ok when exceptions are thrown? Perhaps a simple error text telling the user to double check the types manually.

btw I keep writing probe when i mean sensor. :-)

kbilsted commented 10 months ago

Extracting all of our sensors i get more deserialization errors.

PrtgAPI.XmlDeserializationException HResult=0x80131500 Message=Could not deserialize value '2' as it is not a valid member of type 'PrtgAPI.GraphType'. Could not process XML '2'. Source=PrtgAPI StackTrace: at PrtgAPI.Linq.Expressions.Serialization.XmlExpressionSerializerBase.FailEnum(String s, Type type) in PrtgAPI.Linq.Expressions.Serialization\XmlExpressionSerializerBase.cs:line 119 at PrtgAPI.Request.ResponseParser.GetObjectProperties[T](PrtgResponse response, XmlEngine xmlEngine, ObjectProperty mandatoryProperty) in PrtgAPI.Request\ResponseParser.cs:line 287 at PrtgAPI.PrtgClient.GetObjectProperties[T](Either2 objectOrId, ObjectType objectType, ObjectProperty mandatoryProperty) in PrtgAPI\PrtgClient.cs:line 723 at PrtgAPI.PrtgClient.GetSensorProperties(Either2 sensor) in PrtgAPI\PrtgClient.cs:line 3909 at Program.

$(String[] args) in C:\xxx.prtg alarm xxx\xxx.yyy\Program.cs:line 31

var sensors = client.GetSensors();

Console.WriteLine($"found {sensors.Count} sensors");

Console.WriteLine("name;active;type;tags;url");
foreach (var sensor in sensors)
{
    var props =client.GetSensorProperties(sensor.Id);

    Console.WriteLine($"{sensor.Name};{sensor.Active};{sensor.Type};{string.Join("+",sensor.Tags)};{props.Url}");

    Console.WriteLine(props.Url);
};

the log file does not contain <injected_stack> anythere...

lordmilko commented 10 months ago

It sounds like PRTG has added a new GraphType that is not known to PrtgAPI.

You can work around this for now by doing GetObjectPropertiesRaw instead of GetSensorProperties. This will give you a dictionary of raw values and bypass any enum values that are unknown to PrtgAPI

kbilsted commented 10 months ago

Are you interested in a data dump so the parser can be extended to support the new types?